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 => {
|
const checkFull = board => {
|
||||||
for (const row of board) {
|
for (const row of board) {
|
||||||
for (const col of row) {
|
for (const col of row) {
|
||||||
if (col !== ' ') {
|
if (col === ' ') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,11 @@ module.exports = {
|
||||||
description: 'H I G H R O L L E R',
|
description: 'H I G H R O L L E R',
|
||||||
emoji: '8ball'
|
emoji: '8ball'
|
||||||
},
|
},
|
||||||
|
sigmaBets: {
|
||||||
|
name: 'Make a bet over 100 Quintillion',
|
||||||
|
description: 'Return to monke',
|
||||||
|
emoji: 'yeknom'
|
||||||
|
},
|
||||||
ignited: {
|
ignited: {
|
||||||
name: 'You light my fire, baby',
|
name: 'You light my fire, baby',
|
||||||
description: 'And you pay attention to descriptions!',
|
description: 'And you pay attention to descriptions!',
|
||||||
|
@ -120,6 +125,11 @@ module.exports = {
|
||||||
description: `I mean... that's basically all of them.`,
|
description: `I mean... that's basically all of them.`,
|
||||||
emoji: 'office'
|
emoji: 'office'
|
||||||
},
|
},
|
||||||
|
government100: {
|
||||||
|
name: 'Run 100 Governments',
|
||||||
|
description: `I hope you're using them for something good...`,
|
||||||
|
emoji: 'japanese_castle'
|
||||||
|
},
|
||||||
|
|
||||||
weAllNeedHelp: {
|
weAllNeedHelp: {
|
||||||
name: 'View the \'!coin\' help',
|
name: 'View the \'!coin\' help',
|
||||||
|
@ -159,7 +169,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
bookWorm: {
|
bookWorm: {
|
||||||
name: 'Take a peek at the lore',
|
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'
|
emoji: 'books'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
const buyableItems = require('./buyableItems')
|
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 slack = require('../../slack')
|
||||||
|
|
||||||
const calculateCost = ({ itemName, user, quantity = 1 }) => {
|
const leaderboardUpdater = {}
|
||||||
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 getItemHeader = user => ([itemName, { baseCost, description, emoji }]) => {
|
const getItemHeader = user => ([itemName, { baseCost, description, emoji }]) => {
|
||||||
const itemCost = commas(user ? calculateCost({ itemName, user }) : baseCost)
|
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 ({
|
return ({
|
||||||
text: buyableText(highestCoins, user),
|
text: (extraMessage && extraMessage + '\n')
|
||||||
blocks: Object.entries(buyableItems)
|
+ `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))
|
.filter(([, item]) => canView(item, highestCoins))
|
||||||
.map(([itemName]) => {
|
.map(([itemName]) => {
|
||||||
const cost = calculateCost({ itemName, user, quantity: 1 })
|
const cost = calculateCost({ itemName, user, quantity: 1 })
|
||||||
const cps = Math.round(singleItemCps(user, itemName))
|
const cps = Math.round(singleItemCps(user, itemName))
|
||||||
return ({ user, itemName, cost, cps })
|
return ({ user, itemName, cost, cps })
|
||||||
}).map(buildBlock)
|
}).map(buildBlock)
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +140,7 @@ const buyRoute = async ({ event, say, args, user }) => {
|
||||||
return say(`Buying ${quantity} ${buyableName} would cost you ${commas(realCost)} HVAC`)
|
return say(`Buying ${quantity} ${buyableName} would cost you ${commas(realCost)} HVAC`)
|
||||||
}
|
}
|
||||||
if (currentCoins < realCost) {
|
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
|
return
|
||||||
}
|
}
|
||||||
user.coins -= realCost
|
user.coins -= realCost
|
||||||
|
@ -161,15 +165,20 @@ const buyButton = async ({ body, ack, say, payload }) => {
|
||||||
const user = getUser(event.user)
|
const user = getUser(event.user)
|
||||||
const words = ['', buying, body.actions[0].text]
|
const words = ['', buying, body.actions[0].text]
|
||||||
const [commandName, ...args] = words
|
const [commandName, ...args] = words
|
||||||
|
|
||||||
|
let extraMessage = ''
|
||||||
|
say = async text => extraMessage = text
|
||||||
await buyRoute({ event, say, words, args, commandName, user })
|
await buyRoute({ event, say, words, args, commandName, user })
|
||||||
|
|
||||||
const highestCoins = user.highestEver || user.coins || 1
|
const highestCoins = user.highestEver || user.coins || 1
|
||||||
await slack.app.client.chat.update({
|
await slack.app.client.chat.update({
|
||||||
channel: body.channel.id,
|
channel: body.channel.id,
|
||||||
ts: body.message.ts,
|
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))
|
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,
|
earning: 260,
|
||||||
emoji: 'train2',
|
emoji: 'train2',
|
||||||
description: 'Efficiently ship your most valuable coins.',
|
description: 'Efficiently ship your most valuable coins.',
|
||||||
own100Achievement: 'fire100',
|
own100Achievement: 'train100',
|
||||||
},
|
},
|
||||||
fire: {
|
fire: {
|
||||||
baseCost: 1_400_000,
|
baseCost: 1_400_000,
|
||||||
earning: 1_400,
|
earning: 1_400,
|
||||||
emoji: 'fire',
|
emoji: 'fire',
|
||||||
description: 'Return to the roots of HVAC.',
|
description: 'Return to the roots of HVAC.',
|
||||||
own100Achievement: 'train100',
|
own100Achievement: 'fire100',
|
||||||
},
|
},
|
||||||
boomerang: {
|
boomerang: {
|
||||||
baseCost: 20_000_000,
|
baseCost: 20_000_000,
|
||||||
|
@ -46,7 +46,7 @@ module.exports = {
|
||||||
earning: 44_000,
|
earning: 44_000,
|
||||||
emoji: 'new_moon_with_face',
|
emoji: 'new_moon_with_face',
|
||||||
description: 'Convert dark new-moon energy into HVAC Coins.',
|
description: 'Convert dark new-moon energy into HVAC Coins.',
|
||||||
own100Achievement: 'mirror100',
|
own100Achievement: 'moon100',
|
||||||
},
|
},
|
||||||
butterfly: {
|
butterfly: {
|
||||||
baseCost: 5_100_000_000,
|
baseCost: 5_100_000_000,
|
||||||
|
@ -60,41 +60,48 @@ module.exports = {
|
||||||
earning: 1_600_000,
|
earning: 1_600_000,
|
||||||
emoji: 'mirror',
|
emoji: 'mirror',
|
||||||
description: 'Only by gazing inward can you collect enough Coin to influence the thermostat.',
|
description: 'Only by gazing inward can you collect enough Coin to influence the thermostat.',
|
||||||
own100Achievement: 'quade100',
|
own100Achievement: 'mirror100',
|
||||||
},
|
},
|
||||||
quade: {
|
quade: {
|
||||||
baseCost: 1_000_000_000_000,
|
baseCost: 1_000_000_000_000,
|
||||||
earning: 10_000_000,
|
earning: 10_000_000,
|
||||||
emoji: 'quade',
|
emoji: 'quade',
|
||||||
description: 'Has thumbs capable of physically manipulating the thermostat.',
|
description: 'Has thumbs capable of physically manipulating the thermostat.',
|
||||||
own100Achievement: 'hvacker100',
|
own100Achievement: 'quade100',
|
||||||
},
|
},
|
||||||
hvacker: {
|
hvacker: {
|
||||||
baseCost: 14_000_000_000_000,
|
baseCost: 14_000_000_000_000,
|
||||||
earning: 65_000_000,
|
earning: 65_000_000,
|
||||||
emoji: 'hvacker_angery',
|
emoji: 'hvacker_angery',
|
||||||
description: 'Harness the power of the mad god himself.',
|
description: 'Harness the power of the mad god himself.',
|
||||||
own100Achievement: 'creator100',
|
own100Achievement: 'hvacker100',
|
||||||
},
|
},
|
||||||
creator: {
|
creator: {
|
||||||
baseCost: 170_000_000_000_000,
|
baseCost: 170_000_000_000_000,
|
||||||
earning: 430_000_000,
|
earning: 430_000_000,
|
||||||
emoji: 'question',
|
emoji: 'question',
|
||||||
description: 'The elusive creator of Hvacker takes a favorable look at your CPS.',
|
description: 'The elusive creator of Hvacker takes a favorable look at your CPS.',
|
||||||
own100Achievement: 'smallBusiness100',
|
own100Achievement: 'creator100',
|
||||||
},
|
},
|
||||||
smallBusiness: {
|
smallBusiness: {
|
||||||
baseCost: 2_210_000_000_000_000,
|
baseCost: 2_210_000_000_000_000,
|
||||||
earning: 2_845_000_000,
|
earning: 2_845_000_000,
|
||||||
emoji: 'convenience_store',
|
emoji: 'convenience_store',
|
||||||
description: 'The place where the creator of Hvacker goes to work.',
|
description: 'The place where the creator of Hvacker goes to work.',
|
||||||
own100Achievement: 'bigBusiness100',
|
own100Achievement: 'smallBusiness100',
|
||||||
},
|
},
|
||||||
bigBusiness: {
|
bigBusiness: {
|
||||||
baseCost: 26_210_000_000_000_000,
|
baseCost: 26_210_000_000_000_000,
|
||||||
earning: 23_650_000_000,
|
earning: 23_650_000_000,
|
||||||
emoji: 'office',
|
emoji: 'office',
|
||||||
description: 'The place where the smallBusiness goes to work.',
|
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,
|
userHasCheckedQuackgrade,
|
||||||
fuzzyMatcher,
|
fuzzyMatcher,
|
||||||
addCoins,
|
addCoins,
|
||||||
game: { nfts, squad, users, horrors, stonkMarket },
|
game, updateAll
|
||||||
} = require('./utils')
|
} = require('./utils')
|
||||||
|
const { nfts, squad, users, horrors, stonkMarket, pet } = game
|
||||||
|
const pets = require('./gotcha')
|
||||||
|
|
||||||
const slack = require('../../slack')
|
const slack = require('../../slack')
|
||||||
const buyableItems = require('./buyableItems')
|
const buyableItems = require('./buyableItems')
|
||||||
const upgrades = require('./upgrades')
|
const upgrades = require('./upgrades')
|
||||||
|
@ -42,7 +45,7 @@ const settings = require('./settings')
|
||||||
// })
|
// })
|
||||||
// const read = () => {
|
// const read = () => {
|
||||||
// readline.question(`What do YOU want? `, async want => {
|
// readline.question(`What do YOU want? `, async want => {
|
||||||
// want && await slack.messageSage(want)
|
// want && await slack.messageAdmin(want)
|
||||||
// read()
|
// read()
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
@ -56,8 +59,8 @@ const getUpgradeEmoji = upgrade => upgrade.emoji || buyableItems[upgrade.type].e
|
||||||
const upgradeText = (user, showOwned = false) => {
|
const upgradeText = (user, showOwned = false) => {
|
||||||
const userDoesNotHave = ([upgradeName, upgrade]) => hasUpgrade(user, upgrade, upgradeName) === showOwned
|
const userDoesNotHave = ([upgradeName, upgrade]) => hasUpgrade(user, upgrade, upgradeName) === showOwned
|
||||||
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
|
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
|
||||||
const format = ([key, value]) => `:${getUpgradeEmoji(value)}: *${key}* - ${commas(value.cost)}\n_${value.description}_`
|
const format = ([, value]) => `:${getUpgradeEmoji(value)}: *${value.name}* - ${commas(value.cost)}\n_${value.description}_`
|
||||||
return '\n\n' +
|
const subtotal = '\n\n' +
|
||||||
Object.entries(upgrades)
|
Object.entries(upgrades)
|
||||||
.filter(userDoesNotHave)
|
.filter(userDoesNotHave)
|
||||||
.filter(userMeetsCondition)
|
.filter(userMeetsCondition)
|
||||||
|
@ -65,6 +68,7 @@ const upgradeText = (user, showOwned = false) => {
|
||||||
.join('\n\n') +
|
.join('\n\n') +
|
||||||
'\n\n:grey_question::grey_question::grey_question:' +
|
'\n\n:grey_question::grey_question::grey_question:' +
|
||||||
'\n\nJust type \'!upgrade upgrade_name\' to purchase'
|
'\n\nJust type \'!upgrade upgrade_name\' to purchase'
|
||||||
|
return subtotal.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUpgrade = (user, upgrade, upgradeName) => !!user.upgrades[upgrade.type]?.includes(upgradeName)
|
const hasUpgrade = (user, upgrade, upgradeName) => !!user.upgrades[upgrade.type]?.includes(upgradeName)
|
||||||
|
@ -109,6 +113,15 @@ const commands = new Map()
|
||||||
let commandHelpText = ''
|
let commandHelpText = ''
|
||||||
let shortCommandHelpText = 'Use `!help full` to show details for all commands, or `!<command> help` to show for just one.\n```'
|
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 }
|
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) => {
|
const command = (commandNames, helpText, action, { hidden, condition } = defaultAccess) => {
|
||||||
if (!hidden) {
|
if (!hidden) {
|
||||||
console.log(`Initializing command '${commandNames[0]}'`)
|
console.log(`Initializing command '${commandNames[0]}'`)
|
||||||
|
@ -157,7 +170,7 @@ const getHorrorMessageOdds = (offset = 0) => {
|
||||||
command(
|
command(
|
||||||
['!odds'],
|
['!odds'],
|
||||||
'Show shuffle odds re: !horror',
|
'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)}`
|
const percentOrOneIn = odds => `${(odds * 100).toPrecision(3)}%, or about 1 in ${Math.round(1 / odds)}`
|
||||||
if (!args[0]) {
|
if (!args[0]) {
|
||||||
return say(
|
return say(
|
||||||
|
@ -168,7 +181,7 @@ command(
|
||||||
//`Tomorrow's horror message odds will be ${percentOrOneIn(getHorrorMessageOdds(1))}`
|
//`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(
|
return say(
|
||||||
`Shuffle odds in ${num} days will be ${percentOrOneIn(getShuffleOdds(num))}\n`
|
`Shuffle odds in ${num} days will be ${percentOrOneIn(getShuffleOdds(num))}\n`
|
||||||
//`Horror message odds in ${num} days will be ${percentOrOneIn(getHorrorMessageOdds(num))}`
|
//`Horror message odds in ${num} days will be ${percentOrOneIn(getHorrorMessageOdds(num))}`
|
||||||
|
@ -178,9 +191,9 @@ command(
|
||||||
command(
|
command(
|
||||||
['!shuffle'],
|
['!shuffle'],
|
||||||
'!shuffle daysFromNow message',
|
'!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 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
|
const [, ...message] = args
|
||||||
return say(
|
return say(
|
||||||
`Shuffle odds in ${num} days will be ${percentOrOneIn(getShuffleOdds(num))}\n` +
|
`Shuffle odds in ${num} days will be ${percentOrOneIn(getShuffleOdds(num))}\n` +
|
||||||
|
@ -213,12 +226,12 @@ if (settings.horrorEnabled) {
|
||||||
['!horror'],
|
['!horror'],
|
||||||
'help help help help help',
|
'help help help help help',
|
||||||
async ({ event, say }) => {
|
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()))
|
return slack.postToTechThermostatChannel(shufflePercent(event.text.substring(7).trim(), getShuffleOdds()))
|
||||||
}
|
}
|
||||||
horrors.commandCalls ??= 0
|
horrors.commandCalls ??= 0
|
||||||
horrors.commandCalls += 1
|
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
|
await say('_Do you think you can help me?_') // TODO horror horrors change help to !help
|
||||||
}, { hidden: true })
|
}, { hidden: true })
|
||||||
}
|
}
|
||||||
|
@ -232,7 +245,7 @@ const buildHorrorSay = ({ say, event, commandName, c }) => async message => {
|
||||||
if (shuffled.length > 100 && Math.random() < getShuffleOdds()) {
|
if (shuffled.length > 100 && Math.random() < getShuffleOdds()) {
|
||||||
const middle = (shuffled.length / 2) + Math.round((Math.random() - 1) * (shuffled.length / 5))
|
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)
|
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)
|
await say(shuffled)
|
||||||
} else {
|
} else {
|
||||||
|
@ -244,6 +257,7 @@ const buildSayWithPayload = ({ say, event }) => async msg => {
|
||||||
const payload = {
|
const payload = {
|
||||||
event: {
|
event: {
|
||||||
text: event.text,
|
text: event.text,
|
||||||
|
user: event.user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof(msg) === 'string') {
|
if (typeof(msg) === 'string') {
|
||||||
|
@ -261,7 +275,7 @@ command(
|
||||||
['!!peter-griffin-family-guy'],
|
['!!peter-griffin-family-guy'],
|
||||||
'Delete',
|
'Delete',
|
||||||
async ({ say, user }) => {
|
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.`)
|
return say(`Silly, silly, ${user.name}.\nYou can't just leave us again.`)
|
||||||
}
|
}
|
||||||
user.isDisabled = true
|
user.isDisabled = true
|
||||||
|
@ -269,7 +283,14 @@ command(
|
||||||
return say('.')
|
return say('.')
|
||||||
}, { hidden: true })
|
}, { 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'
|
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') {
|
if (event?.subtype === 'bot_message') {
|
||||||
return botMessageHandler({ event, say })
|
return botMessageHandler({ event, say })
|
||||||
}
|
}
|
||||||
|
const startTime = new Date()
|
||||||
const words = event?.text?.split(/\s+/) || []
|
const words = event?.text?.split(/\s+/) || []
|
||||||
const [commandName, ...args] = words
|
const [commandName, ...args] = words
|
||||||
const c = commands.get(commandName)
|
const c = commands.get(commandName)
|
||||||
if (!c && words[0]?.startsWith('!') && event.user !== slack.users.Sage) {
|
let user = getUser(event.user)
|
||||||
return slack.messageSage(`${slack.users[event.user]} tried to use \`${event.text}\`, if you wanted to add that.`)
|
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
|
const trueSay = say
|
||||||
|
@ -361,27 +406,39 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
||||||
say = buildHorrorSay({say, event, args, commandName, c})
|
say = buildHorrorSay({say, event, args, commandName, c})
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = getUser(event.user)
|
// if (user.isPrestiging) {
|
||||||
if (user.isDisabled) {
|
// return say(`Finish prestiging first!`)
|
||||||
return
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
const hauntOdds = 0.03
|
const hauntOdds = 0.005
|
||||||
const disabledUsers = Object.entries(users).filter(([, user]) => user.isDisabled)
|
const disabledUsers = Object.entries(users).filter(([, user]) => user.isDisabled)
|
||||||
|
|
||||||
let haunted = false
|
let haunted = false
|
||||||
if (disabledUsers.length !== 0) {
|
if (disabledUsers.length === 0) {
|
||||||
if (user.expectingPossession) {
|
user.expectingPossession = false
|
||||||
|
} else {
|
||||||
|
const hauntless = ['!lore']
|
||||||
|
if (user.expectingPossession && !hauntless.includes(commandName)) {
|
||||||
|
console.log(`Haunting ${user.name}`)
|
||||||
user.expectingPossession = false
|
user.expectingPossession = false
|
||||||
//saveGame()
|
//saveGame()
|
||||||
haunted = true
|
haunted = true
|
||||||
const [disabledId, disabledUser] = getRandomFromArray(disabledUsers)
|
const [disabledId] = getRandomFromArray(disabledUsers)
|
||||||
event.user = disabledId
|
event.user = disabledId
|
||||||
user = getUser(event.user)
|
user = getUser(event.user)
|
||||||
if (Math.random() < 0.2) {
|
const userInfo = await slack.app.client.users.info({
|
||||||
say = slack.buildSayPrepend({ say, prepend: `_You feel haunted..._\n_"Hey, it's me, ${(garble(disabledUser.name))}"_\n` })
|
user: disabledId
|
||||||
} else {
|
})
|
||||||
say = slack.buildSayPrepend({ say, prepend: `_You feel haunted..._\n` })
|
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) {
|
} else if (Math.random() < hauntOdds) {
|
||||||
user.expectingPossession = true
|
user.expectingPossession = true
|
||||||
|
@ -390,17 +447,17 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
||||||
say = slack.buildSayPrepend({ say, prepend: `_You feel a chill..._\n` })
|
say = slack.buildSayPrepend({ say, prepend: `_You feel a chill..._\n` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
user.expectingPossession = false
|
|
||||||
}
|
}
|
||||||
user.coins = getCoins(event.user)
|
Object.entries(users).forEach(([id, usr]) => usr.coins = getCoins(id))
|
||||||
const canUse = await c?.condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Sage) })
|
//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) {
|
if (!canUse) {
|
||||||
// const matcher = fuzzyMatcher(commandName)
|
// const matcher = fuzzyMatcher(commandName)
|
||||||
// for (const key of commands.keys()) {
|
// for (const key of commands.keys()) {
|
||||||
// if (matcher.test(key)) {
|
// if (matcher.test(key)) {
|
||||||
// const fetched = commands.get(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}'?`)
|
// //return say(`Did you mean '${key}'?`)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -430,7 +487,7 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
||||||
return
|
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) {
|
if (!isRecycle) {
|
||||||
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
|
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
|
||||||
const defaultOdds = 0.005
|
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)
|
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 }) => {
|
slack.onReaction(async ({ event }) => {
|
||||||
|
@ -517,17 +575,10 @@ const lightning = async ({ user, ms = 5000, channel, multiplier = 1 }) => {
|
||||||
channel: message.channel,
|
channel: message.channel,
|
||||||
ts: message.ts,
|
ts: message.ts,
|
||||||
})
|
})
|
||||||
// await slack.messageSage(`${user.name} failed to bottle some lighting!`)
|
// await slack.messageAdmin(`${user.name} failed to bottle some lighting!`)
|
||||||
}, msToBottle)
|
}, msToBottle)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dedicatedPlayers = [
|
|
||||||
slack.users.Sage,
|
|
||||||
slack.users.Houston,
|
|
||||||
slack.users.Adam,
|
|
||||||
slack.users.Fernando,
|
|
||||||
]
|
|
||||||
|
|
||||||
command(
|
command(
|
||||||
['!bolt'],
|
['!bolt'],
|
||||||
'Send a lighting strike to the given player.',
|
'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 c = getCoins(body.user.id)
|
||||||
const user = getUser(body.user.id)
|
const user = getUser(body.user.id)
|
||||||
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
|
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]
|
payout = (500 + chaosFilter(payout, 1, user)) * strikes[body.message.ts]
|
||||||
addCoins(user, (c + payout) - user.coins)
|
addCoins(user, (c + payout) - user.coins)
|
||||||
delete strikes[body.message.ts]
|
delete strikes[body.message.ts]
|
||||||
saveGame()
|
saveGame('user bottled a lightning strike')
|
||||||
|
|
||||||
await slack.app.client.chat.update({
|
await slack.app.client.chat.update({
|
||||||
channel: body.channel.id,
|
channel: body.channel.id,
|
||||||
|
@ -577,7 +632,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
|
||||||
blocks: []
|
blocks: []
|
||||||
})
|
})
|
||||||
await ack()
|
await ack()
|
||||||
return slack.messageSage(`Lighting bottled by <@${body.user.id}>`)
|
return slack.messageAdmin(`Lighting bottled by <@${body.user.id}>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
slack.onMessage(async msg => {
|
slack.onMessage(async msg => {
|
||||||
|
@ -785,7 +840,7 @@ command(
|
||||||
command(
|
command(
|
||||||
['!a', '!ach', '!achievements'],
|
['!a', '!ach', '!achievements'],
|
||||||
'List your glorious achievements',
|
'List your glorious achievements',
|
||||||
async ({ event, say, user }) => {
|
async ({ event, say, user, args, isAdmin }) => {
|
||||||
const achievementCount = Object.keys(user.achievements).length
|
const achievementCount = Object.keys(user.achievements).length
|
||||||
const prefix = `You have ${achievementCount} achievements!\n\n`
|
const prefix = `You have ${achievementCount} achievements!\n\n`
|
||||||
const mult = (Math.pow(1.01, achievementCount) - 1) * 100
|
const mult = (Math.pow(1.01, achievementCount) - 1) * 100
|
||||||
|
@ -880,36 +935,49 @@ command(['!cps'],
|
||||||
const doMine = async ({ user, userId, say }) => {
|
const doMine = async ({ user, userId, say }) => {
|
||||||
const random = Math.random()
|
const random = Math.random()
|
||||||
const c = user.coins
|
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 diff
|
||||||
let prefix
|
let prefix
|
||||||
if (random > 0.9947) {
|
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`
|
prefix = `:gem: You found a lucky gem worth ${commas(diff)} HVAC!\n`
|
||||||
addAchievement(user, 'luckyGem', say)
|
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) {
|
} 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`
|
prefix = `:goldbrick: You found a lucky gold coin worth ${commas(diff)} HVAC!\n`
|
||||||
addAchievement(user, 'goldBrick', say)
|
addAchievement(user, 'goldBrick', say)
|
||||||
} else if (random > 0.94) {
|
} 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`
|
prefix = `:money_with_wings: You found a lucky green coin worth ${commas(diff)} HVAC!\n`
|
||||||
addAchievement(user, 'greenCoin', say)
|
addAchievement(user, 'greenCoin', say)
|
||||||
} else {
|
} else {
|
||||||
prefix = 'You mined one HVAC.\n'
|
const miningUpgrades = (user.upgrades.mining || []).map(name => upgrades[name])
|
||||||
diff = 1
|
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)
|
addCoins(user, diff)
|
||||||
//saveGame()
|
//saveGame()
|
||||||
return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.`
|
return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lbIndex = 0
|
||||||
command(
|
command(
|
||||||
['!c', '!coin', '!mine', '!'],
|
['!c', '!coin', '!mine', '!'],
|
||||||
'Mine HVAC coins',
|
'Mine HVAC coins',
|
||||||
async ({ say, user, userId }) => {
|
async ({ say, user, userId }) => {
|
||||||
await say(await doMine({ user, userId, say }))
|
await say(await doMine({ user, userId, say }))
|
||||||
|
if ((lbIndex++) % 5 == 0) {
|
||||||
|
return updateAllLeaderboards()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -919,10 +987,11 @@ command(
|
||||||
async ({ event, args, trueSay }) => {
|
async ({ event, args, trueSay }) => {
|
||||||
const [impersonating, ...newWords] = args
|
const [impersonating, ...newWords] = args
|
||||||
event.user = idFromWord(impersonating)
|
event.user = idFromWord(impersonating)
|
||||||
|
getUser(event.user)
|
||||||
const isDisabled = users[event.user].isDisabled
|
const isDisabled = users[event.user].isDisabled
|
||||||
users[event.user].isDisabled = false
|
users[event.user].isDisabled = false
|
||||||
event.text = newWords.join(' ')
|
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
|
users[event.user].isDisabled = isDisabled
|
||||||
}, adminOnly)
|
}, adminOnly)
|
||||||
|
|
||||||
|
@ -951,21 +1020,40 @@ command(
|
||||||
if (user.isDisabled) {
|
if (user.isDisabled) {
|
||||||
user.isDisabled = false
|
user.isDisabled = false
|
||||||
//saveGame()
|
//saveGame()
|
||||||
addAchievement(user, 'theOtherSide', slack.messageSage)
|
addAchievement(user, 'theOtherSide', slack.messageAdmin)
|
||||||
await slack.postToTechThermostatChannel(`_${user.name} has returned..._`)
|
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(
|
command(
|
||||||
['!g', '!gamble'],
|
['!g', '!gamble'],
|
||||||
'Gamble away your HVAC\n' +
|
'Gamble away your HVAC\n' +
|
||||||
' To use, say \'gamble coin_amount\' or \'!gamble all\'',
|
' To use, say \'gamble coin_amount\' or \'!gamble all\'',
|
||||||
async ({ say, args, user }) => {
|
async ({ say, trueSay, args, user, event }) => {
|
||||||
const requestedWager = parseAll(args.join(' '), user.coins)
|
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
|
const n = (chaosFilter(requestedWager, 0.2, user, user.coins) + requestedWager) / 2
|
||||||
if (!n || n < 0) {
|
if (!n || n < 0) {
|
||||||
return say(`Invalid number '${n}'`)
|
return say(`Invalid number '${argText}'`)
|
||||||
}
|
}
|
||||||
if (user.coins < n) {
|
if (user.coins < n) {
|
||||||
return say(`You don't have that many coins! You have ${commas(user.coins)}.`)
|
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) {
|
if (n >= 100_000_000_000_000_000) {
|
||||||
addAchievement(user, 'mondoBets', say)
|
addAchievement(user, 'mondoBets', say)
|
||||||
}
|
}
|
||||||
|
if (n >= 100_000_000_000_000_000_000) {
|
||||||
|
addAchievement(user, 'sigmaBets', say)
|
||||||
|
}
|
||||||
user.coins -= n
|
user.coins -= n
|
||||||
let outcome
|
let outcome
|
||||||
if (Math.random() > 0.5) {
|
if (Math.random() > 0.5) {
|
||||||
|
@ -991,30 +1082,45 @@ command(
|
||||||
//saveGame()
|
//saveGame()
|
||||||
await say(`You bet ${commas(n)} coins and ${outcome}! You now have ${commas(user.coins)}.`)
|
await say(`You bet ${commas(n)} coins and ${outcome}! You now have ${commas(user.coins)}.`)
|
||||||
if (outcome === 'lost' && user.lostBetMessage) {
|
if (outcome === 'lost' && user.lostBetMessage) {
|
||||||
await say(user.lostBetMessage)
|
await trueSay(user.lostBetMessage)
|
||||||
} else if (outcome === 'won' && user.wonBetMessage) {
|
} 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(
|
command(
|
||||||
['!setloss'],
|
['!setloss'],
|
||||||
'!setloss <emoji>',
|
'!setloss <emoji>',
|
||||||
async ({ args, user, say }) => {
|
async ({ args, user, say }) => {
|
||||||
const emoji = args[0]
|
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!`)
|
return say(`Argument must be a single emoji!`)
|
||||||
}
|
}
|
||||||
user.lostBetMessage = emoji
|
user.lostBetMessage = emoji
|
||||||
}, {hidden: true})
|
}, {hidden: true})
|
||||||
|
|
||||||
command(
|
command(
|
||||||
['!setwon'],
|
['!setwon', '!setwin'],
|
||||||
'!setwon <emoji>',
|
'!setwon <emoji>',
|
||||||
async ({ args, user, say }) => {
|
async ({ args, user, say }) => {
|
||||||
const emoji = args[0]
|
const emoji = args[0]
|
||||||
if (!emoji || !emoji.startsWith(':') || !emoji.endsWith(':')) {
|
if (!await validEmoji(emoji)) {
|
||||||
return say(`Argument must be a single emoji!`)
|
return say(`Argument must be a single emoji!`)
|
||||||
}
|
}
|
||||||
user.wonBetMessage = emoji
|
user.wonBetMessage = emoji
|
||||||
|
@ -1052,55 +1158,68 @@ command(
|
||||||
await say(upgradeText(user, true))
|
await say(upgradeText(user, true))
|
||||||
}, dmsOnly)
|
}, dmsOnly)
|
||||||
|
|
||||||
const upgradeText2 = user => {
|
const upgradeText2 = (user, extraMessage = '') => {
|
||||||
const userDoesNotHave = ([upgradeName, upgrade]) => !hasUpgrade(user, upgrade, upgradeName)
|
const userDoesNotHave = ([upgradeName, upgrade]) => !hasUpgrade(user, upgrade, upgradeName)
|
||||||
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
|
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
|
||||||
return ({
|
return ({
|
||||||
text: upgradeText(user, false),
|
text: (extraMessage && extraMessage + '\n') + upgradeText(user, false),
|
||||||
blocks: Object.entries(upgrades)
|
blocks: [
|
||||||
|
(extraMessage && {
|
||||||
|
type: 'section',
|
||||||
|
text: {
|
||||||
|
type: 'mrkdwn',
|
||||||
|
text: extraMessage + '\n'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...Object.entries(upgrades)
|
||||||
.filter(userDoesNotHave)
|
.filter(userDoesNotHave)
|
||||||
.filter(userMeetsCondition)
|
.filter(userMeetsCondition)
|
||||||
.map(([upgradeName]) => upgradeBlock(upgradeName))
|
.map(([upgradeName]) => upgradeBlock(upgradeName))
|
||||||
|
].filter(block => block)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
command(
|
command(
|
||||||
['!upgrade', '!u'],
|
['!upgrade', '!u'],
|
||||||
'Improve the performance of your HVAC-generators.\n' +
|
'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 }) => {
|
async ({ say, args, user }) => {
|
||||||
const upgradeName = args[0]
|
if (!args[0]) {
|
||||||
if (!upgradeName) {
|
|
||||||
return say(upgradeText2(user))
|
return say(upgradeText2(user))
|
||||||
}
|
}
|
||||||
const upgrade = upgrades[upgradeName]
|
console.log({args: args.join(' ')})
|
||||||
if (!upgrade) {
|
const matcher = fuzzyMatcher(args.join(' '))
|
||||||
return say('An upgrade with that name does not exist!')
|
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]) {
|
if (!user.upgrades[upgrade.type]) {
|
||||||
user.upgrades[upgrade.type] = []
|
user.upgrades[upgrade.type] = []
|
||||||
}
|
}
|
||||||
if (hasUpgrade(user, upgrade, upgradeName)) {
|
if (hasUpgrade(user, upgrade, id)) {
|
||||||
return say('You already have that upgrade!')
|
return say(`You already have ${upgrade.name}!`)
|
||||||
}
|
}
|
||||||
if (!upgrade.condition(user, getCompletedSquadgradeNames())) {
|
if (!upgrade.condition(user, getCompletedSquadgradeNames())) {
|
||||||
return say('That item does not exist!')
|
return say('That item does not exist!')
|
||||||
}
|
}
|
||||||
const c = user.coins
|
const c = user.coins
|
||||||
if (c < upgrade.cost) {
|
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.coins -= upgrade.cost
|
||||||
user.upgrades[upgrade.type].push(upgradeName)
|
user.upgrades[upgrade.type].push(id)
|
||||||
//saveGame()
|
//saveGame()
|
||||||
await say(`You bought ${upgradeName}!`)
|
await say(`You bought ${id}!`)
|
||||||
}, dmsOnly)
|
}, dmsOnly)
|
||||||
|
|
||||||
const upgradeBlock = upgradeName => ({
|
const upgradeBlock = upgradeName => {
|
||||||
|
const upgrade = upgrades[upgradeName]
|
||||||
|
return ({
|
||||||
type: 'section',
|
type: 'section',
|
||||||
text: {
|
text: {
|
||||||
type: 'mrkdwn',
|
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: {
|
accessory: {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
|
@ -1112,7 +1231,8 @@ const upgradeBlock = upgradeName => ({
|
||||||
value: 'upgrade_' + upgradeName,
|
value: 'upgrade_' + upgradeName,
|
||||||
action_id: 'upgrade_' + upgradeName
|
action_id: 'upgrade_' + upgradeName
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const upgradeButton = async ({ body, ack, say, payload }) => {
|
const upgradeButton = async ({ body, ack, say, payload }) => {
|
||||||
await ack()
|
await ack()
|
||||||
|
@ -1121,16 +1241,19 @@ const upgradeButton = async ({ body, ack, say, payload }) => {
|
||||||
const event = {
|
const event = {
|
||||||
user: body.user.id
|
user: body.user.id
|
||||||
}
|
}
|
||||||
const user = getUser(event.user)
|
const user = getUser(event.user, true)
|
||||||
const words = ['!upgrade', upgrade]
|
const words = ['!upgrade', upgrade]
|
||||||
const [commandName, ...args] = words
|
const [commandName, ...args] = words
|
||||||
|
let extraMessage = ''
|
||||||
|
say = async text => extraMessage = text
|
||||||
await commands.get('!u').action({ event, say, words, args, commandName, user })
|
await commands.get('!u').action({ event, say, words, args, commandName, user })
|
||||||
//const highestCoins = user.highestEver || user.coins || 1
|
//const highestCoins = user.highestEver || user.coins || 1
|
||||||
await slack.app.client.chat.update({
|
await slack.app.client.chat.update({
|
||||||
channel: body.channel.id,
|
channel: body.channel.id,
|
||||||
ts: body.message.ts,
|
ts: body.message.ts,
|
||||||
...upgradeText2(user)
|
...upgradeText2(user, extraMessage)
|
||||||
})
|
})
|
||||||
|
await updateAllLeaderboards()
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(upgrades).forEach(upgradeName => slack.app.action('upgrade_' + upgradeName, upgradeButton))
|
Object.keys(upgrades).forEach(upgradeName => slack.app.action('upgrade_' + upgradeName, upgradeButton))
|
||||||
|
@ -1146,15 +1269,13 @@ const getCurrentSquadgrade = () => {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
|
upgrade,
|
||||||
remaining: squad.upgrades[name],
|
remaining: squad.upgrades[name],
|
||||||
emoji: upgrade.emoji,
|
emoji: upgrade.emoji,
|
||||||
description: upgrade.description
|
description: upgrade.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const squadgradeText = ([name, { emoji, description }]) =>
|
|
||||||
`:${emoji}: *${name}*\n_${description}_`
|
|
||||||
|
|
||||||
const squadText = () => {
|
const squadText = () => {
|
||||||
const current = getCurrentSquadgrade()
|
const current = getCurrentSquadgrade()
|
||||||
if (current) {
|
if (current) {
|
||||||
|
@ -1163,8 +1284,6 @@ const squadText = () => {
|
||||||
return currentUpgradeText(current)
|
return currentUpgradeText(current)
|
||||||
}
|
}
|
||||||
return 'No more squadgrades currently available.'
|
return 'No more squadgrades currently available.'
|
||||||
// const squadIsMissing = ([name, upgrade]) => squad.upgrades[name] === false
|
|
||||||
// return squadgradeText(Object.entries(squadUpgrades).find(squadIsMissing))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command(
|
command(
|
||||||
|
@ -1181,7 +1300,7 @@ command(
|
||||||
return say('No squadgrades are currently available')
|
return say('No squadgrades are currently available')
|
||||||
}
|
}
|
||||||
const currentCoins = user.coins
|
const currentCoins = user.coins
|
||||||
let amount = parseAll(args.join(' '), currentCoins)
|
let amount = parseAll(args.join(' '), currentCoins, user)
|
||||||
if (amount > currentCoins) {
|
if (amount > currentCoins) {
|
||||||
return say(`You don't have that much HVAC! You have ${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(
|
command(
|
||||||
['!buy', '!b', '?b', '?buy'],
|
['!buy', '!b', '?b', '?buy'],
|
||||||
'Buy new items to earn HVAC with\n' +
|
'Buy new items to earn HVAC with\n' +
|
||||||
|
@ -1270,7 +1389,7 @@ command(
|
||||||
}
|
}
|
||||||
let [target, ...amountText] = args
|
let [target, ...amountText] = args
|
||||||
amountText = amountText.join(' ')
|
amountText = amountText.join(' ')
|
||||||
const amount = parseAll(amountText, user.coins)
|
const amount = parseAll(amountText, user.coins, user)
|
||||||
const targetId = idFromWord(target)
|
const targetId = idFromWord(target)
|
||||||
if (!amount || amount < 0) {
|
if (!amount || amount < 0) {
|
||||||
return say('Amount must be a positive integer!')
|
return say('Amount must be a positive integer!')
|
||||||
|
@ -1281,10 +1400,6 @@ command(
|
||||||
if (user.coins < amount) {
|
if (user.coins < amount) {
|
||||||
return say(`You don't have that many coins! You have ${commas(user.coins)} HVAC.`)
|
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) {
|
if (amountText === 'all' && slack.users.Tyler === targetId) {
|
||||||
addAchievement(user, 'walmartGiftCard', say)
|
addAchievement(user, 'walmartGiftCard', say)
|
||||||
}
|
}
|
||||||
|
@ -1320,7 +1435,7 @@ command(
|
||||||
['!gimme'],
|
['!gimme'],
|
||||||
'Give self x coins',
|
'Give self x coins',
|
||||||
async ({ say, args, user }) => {
|
async ({ say, args, user }) => {
|
||||||
const increase = parseInt(args[0].replace(/,/g, ''))
|
const increase = parseAll(args.join(' '))
|
||||||
addCoins(user, increase)
|
addCoins(user, increase)
|
||||||
await say(`You now have ${user.coins} HVAC.`)
|
await say(`You now have ${user.coins} HVAC.`)
|
||||||
}, testOnly)
|
}, testOnly)
|
||||||
|
@ -1350,23 +1465,23 @@ const buildPEmoji = name => {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
const prestigeEmojis = [
|
const prestigeEmojis = [
|
||||||
buildPEmoji('rock'),
|
'rock',
|
||||||
buildPEmoji('wood'),
|
'wood',
|
||||||
buildPEmoji('seedling'),
|
'seedling',
|
||||||
buildPEmoji('evergreen_tree'),
|
'evergreen_tree',
|
||||||
buildPEmoji('hibiscus'),
|
'hibiscus',
|
||||||
buildPEmoji('thunder_cloud_and_rain'),
|
'thunder_cloud_and_rain',
|
||||||
buildPEmoji('rainbow'),
|
'rainbow',
|
||||||
buildPEmoji('star'),
|
'star',
|
||||||
buildPEmoji('dizzy'),
|
'dizzy',
|
||||||
buildPEmoji('sparkles'),
|
'sparkles',
|
||||||
buildPEmoji('star2'),
|
'star2',
|
||||||
buildPEmoji('stars'),
|
'stars',
|
||||||
buildPEmoji('comet'),
|
'comet',
|
||||||
buildPEmoji('night_with_stars'),
|
'night_with_stars',
|
||||||
buildPEmoji('milky_way'),
|
'milky_way',
|
||||||
buildPEmoji('eye'),
|
'eye',
|
||||||
]
|
].map(buildPEmoji)
|
||||||
|
|
||||||
const prestigeEmoji = user => {
|
const prestigeEmoji = user => {
|
||||||
const p = user.prestige || 0
|
const p = user.prestige || 0
|
||||||
|
@ -1388,7 +1503,7 @@ command(
|
||||||
`Show the emoji for each prestige level you've been through.`,
|
`Show the emoji for each prestige level you've been through.`,
|
||||||
async ({ say, user, event, args }) => {
|
async ({ say, user, event, args }) => {
|
||||||
let p = user.prestige || 0
|
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
|
p = 99999999
|
||||||
}
|
}
|
||||||
let message = ''
|
let message = ''
|
||||||
|
@ -1402,16 +1517,18 @@ command(
|
||||||
}, prestigeOnly)
|
}, prestigeOnly)
|
||||||
|
|
||||||
command(
|
command(
|
||||||
['!leaderboard', '!lb'],
|
['!pet'],
|
||||||
'Show the top HVAC-earners, ranked by prestige, then CPS',
|
`Take care of the office pet!\nPet bonuses are shared between all players!`,
|
||||||
async ({ say, user }) => {
|
async ({ say, user, event, args }) => {
|
||||||
// if ((event.user === slack.users.Houston || event.user === slack.users.Sage) && event.channel_type.includes('im')) {
|
pets.petToText(pet, null, say)
|
||||||
// return say('```' + `Hvacker - 9 souls - Taking from them whatever it desires\nSome other losers - who cares - whatever` + '```')
|
})
|
||||||
// }
|
|
||||||
|
const strike = user => user.isDisabled ? '~' : ''
|
||||||
|
|
||||||
|
const generateLeaderboard = ({ args }) => {
|
||||||
let index = 1
|
let index = 1
|
||||||
await say(
|
return Object.entries(users)
|
||||||
Object.entries(users)
|
.filter(([, user]) => (!user.isDisabled || args[0] === 'all') && (Object.entries(user.items).length > 0 || user.prestige))
|
||||||
.filter(([, user]) => !user.isDisabled && (Object.entries(user.items).length > 0 || user.prestige))
|
|
||||||
.sort(([id, user1], [id2, user2]) => {
|
.sort(([id, user1], [id2, user2]) => {
|
||||||
const leftPrestige = getUser(id).prestige
|
const leftPrestige = getUser(id).prestige
|
||||||
const rightPrestige = getUser(id2).prestige
|
const rightPrestige = getUser(id2).prestige
|
||||||
|
@ -1423,21 +1540,43 @@ command(
|
||||||
}
|
}
|
||||||
return getCPS(user1) > getCPS(user2)
|
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')
|
.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 hints = []
|
||||||
const oneShot = (name, helpText, message, achievementName) => {
|
const oneShot = (name, helpText, message, achievementName) => {
|
||||||
if (helpText) {
|
if (helpText) {
|
||||||
hints.push(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 say(message)
|
||||||
await slack.messageSage(`Wow buddy they like your ${name} joke.`)
|
await slack.messageAdmin(`Wow buddy they like your ${name} joke.`)
|
||||||
if (achievementName) {
|
if (achievementName) {
|
||||||
addAchievement(user, achievementName, say)
|
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('!sugma', 'Not very original.', ':hvacker_angery:')
|
||||||
oneShot('!pog', 'One poggers hvacker', '<https://i.imgur.com/XCg7WDz.png|poggers>')
|
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('!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('!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('!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!>')
|
// 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(
|
command(
|
||||||
|
@ -1493,13 +1633,27 @@ command(
|
||||||
'Confirm your prestige activation.',
|
'Confirm your prestige activation.',
|
||||||
prestige.prestigeConfirmRoute, { hidden: true })
|
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(
|
command(
|
||||||
['!quack', '!quackstore'],
|
['!quack', '!quackstore'],
|
||||||
'Spend your prestigious quackings\n\n' +
|
'Confirm your prestige activation.',
|
||||||
'Say \'!quack upgrade_name\' to purchase a quackgrade',
|
async ({ event, say, user, YEET }) => {
|
||||||
prestige.quackStoreRoute,
|
await prestige.prestigeConfirmRoute({ event, say, user, YEET: true})
|
||||||
prestigeOnly
|
}, prestigeOnly)
|
||||||
)
|
|
||||||
|
// command(
|
||||||
|
// ['!quack', '!quackstore'],
|
||||||
|
// 'Spend your prestigious quackings\n\n' +
|
||||||
|
// 'Say \'!quack upgrade_name\' to purchase a quackgrade',
|
||||||
|
// prestige.quackStoreRoute,
|
||||||
|
// prestigeOnly
|
||||||
|
// )
|
||||||
|
|
||||||
command(
|
command(
|
||||||
['!myquack', '!myq'],
|
['!myquack', '!myq'],
|
||||||
|
@ -1592,7 +1746,7 @@ command(
|
||||||
return say('What, are you trying to steal from yourself? What, are you stupid?')
|
return say('What, are you trying to steal from yourself? What, are you stupid?')
|
||||||
}
|
}
|
||||||
if (!targetId) {
|
if (!targetId) {
|
||||||
targetId = slack.users.Sage
|
targetId = slack.users.Admin
|
||||||
}
|
}
|
||||||
if (user.coins < amount) {
|
if (user.coins < amount) {
|
||||||
return
|
return
|
||||||
|
@ -1609,7 +1763,7 @@ command(
|
||||||
async ({ event, say, user }) => {
|
async ({ event, say, user }) => {
|
||||||
const currentDate = new Date().getDate()
|
const currentDate = new Date().getDate()
|
||||||
const lastLotto = user.lastLotto === undefined ? currentDate - 1 : user.lastLotto
|
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?')
|
return say('Hey, only one lotto ticket per day, alright?')
|
||||||
}
|
}
|
||||||
let msg
|
let msg
|
||||||
|
@ -1635,8 +1789,8 @@ command(
|
||||||
command(
|
command(
|
||||||
['!giveach'],
|
['!giveach'],
|
||||||
'!giveach @player ach_name',
|
'!giveach @player ach_name',
|
||||||
async ({ args, say, user }) => {
|
async ({ args, say, }) => {
|
||||||
addAchievement(user, args[1], say)
|
addAchievement(getUser(idFromWord(args[0])), args[1], say)
|
||||||
//saveGame()
|
//saveGame()
|
||||||
}, adminOnly)
|
}, adminOnly)
|
||||||
|
|
||||||
|
@ -1646,6 +1800,45 @@ command(
|
||||||
async ({ args, say }) => say(`<@${args[0]}>`),
|
async ({ args, say }) => say(`<@${args[0]}>`),
|
||||||
adminOnly)
|
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(
|
command(
|
||||||
['!ngift'],
|
['!ngift'],
|
||||||
'!ngift player_id nft_name',
|
'!ngift player_id nft_name',
|
||||||
|
@ -1741,20 +1934,21 @@ const updateStonkPrices = () => {
|
||||||
for (let i = stonkMarket.lastDay; i < today; i++) {
|
for (let i = stonkMarket.lastDay; i < today; i++) {
|
||||||
console.log('set lastPrice')
|
console.log('set lastPrice')
|
||||||
stonk.lastPrice = stonk.price
|
stonk.lastPrice = stonk.price
|
||||||
|
console.log(stonk.pattern, stonkPatterns)
|
||||||
stonk.price *= 1 + (stonkPatterns[stonk.pattern][stonk.index] / 100)
|
stonk.price *= 1 + (stonkPatterns[stonk.pattern][stonk.index] / 100)
|
||||||
stonk.index++
|
stonk.index++
|
||||||
if (stonk.index >= stonkPatterns[stonk.pattern].length) {
|
if (stonk.index >= stonkPatterns[stonk.pattern]?.length) {
|
||||||
stonk.index = 0
|
stonk.index = 0
|
||||||
stonk.pattern = nextPattern(stonk.pattern)
|
stonk.pattern = nextPattern(stonk.pattern)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
stonkMarket.lastDay = today
|
stonkMarket.lastDay = today
|
||||||
//saveGame(true)
|
//saveGame(null, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const buyStonks = (user, stonkName, quantityPhrase) => {
|
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) {
|
if (!quantity) {
|
||||||
return 'Quantity must be positive integer!'
|
return 'Quantity must be positive integer!'
|
||||||
}
|
}
|
||||||
|
@ -1773,7 +1967,7 @@ const buyStonks = (user, stonkName, quantityPhrase) => {
|
||||||
const sellStonks = (user, stonkName, quantityPhrase) => {
|
const sellStonks = (user, stonkName, quantityPhrase) => {
|
||||||
user.holdings ??= {}
|
user.holdings ??= {}
|
||||||
user.holdings[stonkName] ??= 0
|
user.holdings[stonkName] ??= 0
|
||||||
const quantity = parseAll(quantityPhrase, user.holdings[stonkName])
|
const quantity = parseAll(quantityPhrase, user.holdings[stonkName], user)
|
||||||
if (!quantity) {
|
if (!quantity) {
|
||||||
return 'Quantity must be positive integer!'
|
return 'Quantity must be positive integer!'
|
||||||
}
|
}
|
||||||
|
@ -1847,10 +2041,16 @@ command(
|
||||||
if (event.text.length > 64 + '!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}!`)
|
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
|
user.lastSpeech = today
|
||||||
const message = event.text.substring(7)
|
const message = event.text.substring(7)
|
||||||
await slack.postToTechThermostatChannel(message)
|
await slack.postToTechThermostatChannel(message)
|
||||||
//saveGame(true)
|
//saveGame(null, true)
|
||||||
}, {
|
}, {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
condition: ({ user }) => userHasCheckedQuackgrade(user, 'theVoice')
|
condition: ({ user }) => userHasCheckedQuackgrade(user, 'theVoice')
|
||||||
|
@ -1882,7 +2082,7 @@ command(
|
||||||
const achName = args.join(' ')
|
const achName = args.join(' ')
|
||||||
const matcher = fuzzyMatcher(achName)
|
const matcher = fuzzyMatcher(achName)
|
||||||
const achievement = Object.entries(achievements)
|
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]]) {
|
if (!achievement || !user.achievements[achievement[0]]) {
|
||||||
return say(`You don't have any achievement matching '${achName}'`)
|
return say(`You don't have any achievement matching '${achName}'`)
|
||||||
}
|
}
|
||||||
|
@ -1896,4 +2096,19 @@ command(
|
||||||
{ hidden: true }
|
{ 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')
|
const slack = require('../../slack')
|
||||||
|
|
||||||
let loreCount = 0
|
let loreCount = 0
|
||||||
|
@ -41,6 +41,7 @@ const lore = [
|
||||||
l(`https://i.imgur.com/eFreg7Y.gif\n`),
|
l(`https://i.imgur.com/eFreg7Y.gif\n`),
|
||||||
|
|
||||||
//l(`As you might imagine, the ninth egg was I, the almighty Hvacker.`)
|
//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 }) => {
|
slack.onReaction(async ({ event, say }) => {
|
||||||
|
@ -61,7 +62,7 @@ slack.onReaction(async ({ event, say }) => {
|
||||||
console.log('lore:', lore[user.lore])
|
console.log('lore:', lore[user.lore])
|
||||||
await say(lore[user.lore].correctResponse)
|
await say(lore[user.lore].correctResponse)
|
||||||
user.lore += 1
|
user.lore += 1
|
||||||
saveGame()
|
saveGame(`updating ${user.name}'s lore counter`)
|
||||||
} catch (e) {console.error('onReaction error', e)}
|
} 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 { quackStore } = require('./quackstore')
|
||||||
|
const buyableItems = require('./buyableItems')
|
||||||
|
const slack = require('../../slack')
|
||||||
|
|
||||||
const possiblePrestige = coins => {
|
const possiblePrestige = coins => {
|
||||||
let p = 0
|
let p = 0
|
||||||
|
@ -26,17 +28,27 @@ const prestigeRoute = async ({ say, args, user }) => {
|
||||||
'Say \'!!prestige me\' to confirm.'
|
'Say \'!!prestige me\' to confirm.'
|
||||||
)
|
)
|
||||||
} else {
|
} 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(
|
await say(
|
||||||
`Current Prestige: ${commas(current)}\n\n` +
|
`Current Prestige: ${commas(current)}\n\n` +
|
||||||
`Quacks gained if you prestige now: ${commas(possible - 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.' +
|
'Say \'!prestige me\' to start the prestige process.' +
|
||||||
`\n\nYour prestige is currently boosting your CPS by ${commas((prestigeMultiplier(user) - 1) * 100)}%`
|
`\n\nYour prestige is currently boosting your CPS by ${commas((prestigeMultiplier(user) - 1) * 100)}%`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}//, true, adminOnly)
|
}//, 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 possible = possiblePrestige(user.coinsAllTime)
|
||||||
const current = user.prestige
|
const current = user.prestige
|
||||||
if (possible <= current) {
|
if (possible <= current) {
|
||||||
|
@ -49,18 +61,23 @@ const prestigeConfirmRoute = async ({ event, say, user }) => {
|
||||||
}
|
}
|
||||||
await makeBackup()
|
await makeBackup()
|
||||||
|
|
||||||
|
user.isPrestiging = true
|
||||||
|
|
||||||
user.quacks ??= 0
|
user.quacks ??= 0
|
||||||
user.quacks += (possible - user.prestige)
|
user.quacks += (possible - user.prestige)
|
||||||
|
|
||||||
user.prestige = possible
|
user.prestige = possible
|
||||||
user.highestEver = 0
|
user.highestEver = 0
|
||||||
user.coins = 0
|
user.coins = 0
|
||||||
user.items = {};
|
user.items = {}
|
||||||
|
user.holdings = {}
|
||||||
const starterUpgrades = (user.quackUpgrades?.starter || [])
|
const starterUpgrades = (user.quackUpgrades?.starter || [])
|
||||||
starterUpgrades.forEach(upgradeName => quackStore[upgradeName].effect(user))
|
starterUpgrades.forEach(upgradeName => quackStore[upgradeName].effect(user))
|
||||||
user.upgrades = {}
|
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]) =>
|
const quackStoreListing = (showCost = true) => ([name, upgrade]) =>
|
||||||
|
@ -75,7 +92,6 @@ const hasPreReqs = user => ([name, upgrade]) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const allUserUpgrades = allUserQuackUpgrades(user)
|
const allUserUpgrades = allUserQuackUpgrades(user)
|
||||||
console.log('allUserUpgrades', allUserUpgrades)
|
|
||||||
return upgrade.preReqs.every(preReq => allUserUpgrades.includes(preReq))
|
return upgrade.preReqs.every(preReq => allUserUpgrades.includes(preReq))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,19 +109,18 @@ const quackStoreText = user =>
|
||||||
`\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
|
`\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
|
||||||
`\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%`
|
`\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 ??= {}
|
user.quackUpgrades ??= {}
|
||||||
const quacks = user.quacks ??= 0
|
if (!args[0] || !YEET) {
|
||||||
if (!args[0]) {
|
|
||||||
await say(quackStoreText(user))
|
await say(quackStoreText(user))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log(`Trying to buy ${args[0]}`)
|
|
||||||
const quackItem = quackStore[args[0]]
|
const quackItem = quackStore[args[0]]
|
||||||
if (!quackItem || !unownedQuackItems(user).find(([name]) => name === args[0])) {
|
if (!quackItem || !unownedQuackItems(user).find(([name]) => name === args[0])) {
|
||||||
await say(`'${args[0]}' is not available in the quack store!`)
|
await say(`'${args[0]}' is not available in the quack store!`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const quacks = user.quacks ??= 0
|
||||||
if (quackItem.cost > quacks) {
|
if (quackItem.cost > quacks) {
|
||||||
await say(`${args[0]} costs ${quackItem.cost} Quacks, but you only have ${quacks}!`)
|
await say(`${args[0]} costs ${quackItem.cost} Quacks, but you only have ${quacks}!`)
|
||||||
return
|
return
|
||||||
|
@ -117,7 +132,151 @@ const quackStoreRoute = async ({ user, say, args }) => {
|
||||||
quackItem.effect(user)
|
quackItem.effect(user)
|
||||||
}
|
}
|
||||||
await say(`You bought ${args[0]}!`)
|
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 =>
|
const ownedQuacksText = user =>
|
||||||
|
@ -137,5 +296,6 @@ module.exports = {
|
||||||
quackStoreRoute,
|
quackStoreRoute,
|
||||||
prestigeRoute,
|
prestigeRoute,
|
||||||
prestigeConfirmRoute,
|
prestigeConfirmRoute,
|
||||||
|
prestigeMenuRoute,
|
||||||
ownedQuacksRoute
|
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 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 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) + 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 = {
|
const quackStore = {
|
||||||
ascent: {
|
ascent: {
|
||||||
|
@ -18,7 +18,7 @@ const quackStore = {
|
||||||
name: 'Nuclear Fuel',
|
name: 'Nuclear Fuel',
|
||||||
type: 'cps',
|
type: 'cps',
|
||||||
emoji: 'atom_symbol',
|
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'],
|
preReqs: ['ascent'],
|
||||||
effect: cps => cps * 1.2,
|
effect: cps => cps * 1.2,
|
||||||
cost: 5
|
cost: 5
|
||||||
|
@ -70,12 +70,12 @@ const quackStore = {
|
||||||
type: 'starter',
|
type: 'starter',
|
||||||
emoji: 'baby_symbol',
|
emoji: 'baby_symbol',
|
||||||
description: 'Start each prestige with 5 mice',
|
description: 'Start each prestige with 5 mice',
|
||||||
preReqs: ['dryerSheet', 'chaos'],
|
preReqs: ['ascent'],
|
||||||
effect: user => {
|
effect: user => {
|
||||||
user.items.mouse ??= 0
|
user.items.mouse ??= 0
|
||||||
user.items.mouse += 5
|
user.items.mouse += 5
|
||||||
},
|
},
|
||||||
cost: 5
|
cost: 4
|
||||||
},
|
},
|
||||||
|
|
||||||
silverSpoon: {
|
silverSpoon: {
|
||||||
|
@ -88,11 +88,11 @@ const quackStore = {
|
||||||
user.items.accountant ??= 0
|
user.items.accountant ??= 0
|
||||||
user.items.accountant += 5
|
user.items.accountant += 5
|
||||||
},
|
},
|
||||||
cost: 10
|
cost: 16
|
||||||
},
|
},
|
||||||
|
|
||||||
oceanMan: {
|
sharkBoy: {
|
||||||
name: 'Ocean Man',
|
name: 'Shark Boy',
|
||||||
type: 'starter',
|
type: 'starter',
|
||||||
emoji: 'ocean',
|
emoji: 'ocean',
|
||||||
description: 'Start each prestige with 5 whales',
|
description: 'Start each prestige with 5 whales',
|
||||||
|
@ -101,8 +101,73 @@ const quackStore = {
|
||||||
user.items.whale ??= 0
|
user.items.whale ??= 0
|
||||||
user.items.whale += 5
|
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 = {
|
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,
|
type,
|
||||||
description,
|
description,
|
||||||
condition: (user, squadGrades) => user.items[type] >= count && extraCondition(user, squadGrades),
|
condition: (user, squadGrades) => user.items[type] >= count && extraCondition(user, squadGrades),
|
||||||
|
@ -6,7 +9,8 @@ const basic = ({ type, description, count, cost, extraCondition = () => true, ef
|
||||||
effect
|
effect
|
||||||
})
|
})
|
||||||
|
|
||||||
const evil = ({ type, description, cost }) => basic({
|
const evil = ({ name, type, description, cost }) => basic({
|
||||||
|
name,
|
||||||
type,
|
type,
|
||||||
description,
|
description,
|
||||||
count: 40,
|
count: 40,
|
||||||
|
@ -14,7 +18,8 @@ const evil = ({ type, description, cost }) => basic({
|
||||||
extraCondition: (user, squadGrades) => squadGrades?.includes('discardHumanMorals'),
|
extraCondition: (user, squadGrades) => squadGrades?.includes('discardHumanMorals'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const heavenly = ({ type, description, cost, multiplier = 2 }) => ({
|
const heavenly = ({ name, type, description, cost, multiplier = 2 }) => ({
|
||||||
|
name,
|
||||||
type,
|
type,
|
||||||
description,
|
description,
|
||||||
condition: (user, squadGrades) => user.items[type] >= 60 && squadGrades?.includes('redemption'),
|
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 disabled = () => false
|
||||||
|
|
||||||
const baby = ({ type, description, cost }) => basic({
|
const baby = ({ name, type, description, cost }) => basic({
|
||||||
|
name,
|
||||||
type,
|
type,
|
||||||
description,
|
description,
|
||||||
count: 70,
|
count: 70,
|
||||||
|
@ -32,7 +38,8 @@ const baby = ({ type, description, cost }) => basic({
|
||||||
extraCondition: disabled
|
extraCondition: disabled
|
||||||
})
|
})
|
||||||
|
|
||||||
const geometry = ({ type, description, cost }) => basic({
|
const geometry = ({ name, type, description, cost }) => basic({
|
||||||
|
name,
|
||||||
type,
|
type,
|
||||||
description,
|
description,
|
||||||
count: 100,
|
count: 100,
|
||||||
|
@ -40,7 +47,8 @@ const geometry = ({ type, description, cost }) => basic({
|
||||||
extraCondition: disabled
|
extraCondition: disabled
|
||||||
})
|
})
|
||||||
|
|
||||||
const universitality = ({ type, description, cost }) => basic({
|
const universitality = ({ name, type, description, cost }) => basic({
|
||||||
|
name,
|
||||||
type,
|
type,
|
||||||
description,
|
description,
|
||||||
count: 100,
|
count: 100,
|
||||||
|
@ -50,134 +58,157 @@ const universitality = ({ type, description, cost }) => basic({
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
doubleClick: basic({
|
doubleClick: basic({
|
||||||
|
name: 'Double-Click',
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
description: 'Doubles the power of mice',
|
description: 'Doubles the power of mice',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 1_000
|
cost: 1_000
|
||||||
}),
|
}),
|
||||||
stinkierCheese: basic({
|
stinkierCheese: basic({
|
||||||
|
name: 'Stinkier Cheese',
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
description: 'Mice are doubly motivated to hunt down HVAC Coins',
|
description: 'Mice are doubly motivated to hunt down HVAC Coins',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 21_000
|
cost: 21_000
|
||||||
}),
|
}),
|
||||||
biggerTeeth: basic({
|
biggerTeeth: basic({
|
||||||
|
name: 'Bigger Teeth',
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
description: 'Mice can intimidate twice as much HVAC out of their victims.',
|
description: 'Mice can intimidate twice as much HVAC out of their victims.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 50_000
|
cost: 50_000
|
||||||
}),
|
}),
|
||||||
rats: evil({
|
rats: evil({
|
||||||
|
name: 'Rats',
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
description: 'Consume the rotten remains of your foes',
|
description: 'Consume the rotten remains of your foes',
|
||||||
cost: 150_000,
|
cost: 150_000,
|
||||||
}),
|
}),
|
||||||
hoodedMice: heavenly({
|
hoodedMice: heavenly({
|
||||||
|
name: 'Hooded Mice',
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
description: 'These monks have nearly reached enlightenment. 10x Mouse CPS.',
|
description: 'These monks have nearly reached enlightenment. 10x Mouse CPS.',
|
||||||
cost: 1_000_000,
|
cost: 1_000_000,
|
||||||
multiplier: 10,
|
multiplier: 10,
|
||||||
}),
|
}),
|
||||||
babyMouse: baby({
|
babyMouse: baby({
|
||||||
|
name: 'Baby Mouse',
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
description: 'Squeak!',
|
description: 'Squeak!',
|
||||||
cost: 6_000_000,
|
cost: 6_000_000,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fasterComputers: basic({
|
fasterComputers: basic({
|
||||||
|
name: 'Faster Computers',
|
||||||
type: 'accountant',
|
type: 'accountant',
|
||||||
description: 'Accountants can ~steal~ optimize twice as much HVAC!',
|
description: 'Accountants can ~steal~ optimize twice as much HVAC!',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 11_000,
|
cost: 11_000,
|
||||||
}),
|
}),
|
||||||
lackOfMorality: basic({
|
lackOfMorality: basic({
|
||||||
|
name: 'Lack of Morality',
|
||||||
type: 'accountant',
|
type: 'accountant',
|
||||||
description: 'Accountants are taking a hint from nearby CEOs.',
|
description: 'Accountants are taking a hint from nearby CEOs.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 200_000,
|
cost: 200_000,
|
||||||
}),
|
}),
|
||||||
widerBrains: basic({
|
widerBrains: basic({
|
||||||
|
name: 'Wider Brains',
|
||||||
type: 'accountant',
|
type: 'accountant',
|
||||||
description: 'For accountant do double of thinking.',
|
description: 'For accountant do double of thinking.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 550_000,
|
cost: 550_000,
|
||||||
}),
|
}),
|
||||||
vastLayoffs: evil({
|
vastLayoffs: evil({
|
||||||
|
name: 'Vast Layoffs',
|
||||||
type: 'accountant',
|
type: 'accountant',
|
||||||
description: 'The weak are not part of our future.',
|
description: 'The weak are not part of our future.',
|
||||||
cost: 2_450_000,
|
cost: 2_450_000,
|
||||||
}),
|
}),
|
||||||
charityFund: heavenly({
|
charityFund: heavenly({
|
||||||
|
name: 'Charity Fund',
|
||||||
type: 'accountant',
|
type: 'accountant',
|
||||||
description: 'THIS one is more than just a tax break. 9x Accountant CPS.',
|
description: 'THIS one is more than just a tax break. 9x Accountant CPS.',
|
||||||
cost: 16_333_333,
|
cost: 16_333_333,
|
||||||
multiplier: 9,
|
multiplier: 9,
|
||||||
}),
|
}),
|
||||||
mathBaby: baby({
|
mathBaby: baby({
|
||||||
|
name: 'Math Baby',
|
||||||
type: 'accountant',
|
type: 'accountant',
|
||||||
description: '2 + 2 = WAAH!',
|
description: '2 + 2 = WAAH!',
|
||||||
cost: 99_999_999,
|
cost: 99_999_999,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
biggerBlowhole: basic({
|
biggerBlowhole: basic({
|
||||||
|
name: 'Bigger Blowhole',
|
||||||
type: 'whale',
|
type: 'whale',
|
||||||
description: 'With all that extra air, whales have double power!',
|
description: 'With all that extra air, whales have double power!',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 120_000
|
cost: 120_000
|
||||||
}),
|
}),
|
||||||
sassyWhales: basic({
|
sassyWhales: basic({
|
||||||
|
name: 'Sassy Whales',
|
||||||
type: 'whale',
|
type: 'whale',
|
||||||
description: 'These are the kind of whales that know how to get twice as much done',
|
description: 'These are the kind of whales that know how to get twice as much done',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 3_000_000
|
cost: 3_000_000
|
||||||
}),
|
}),
|
||||||
thinnerWater: basic({
|
thinnerWater: basic({
|
||||||
|
name: 'Thinner Water',
|
||||||
type: 'whale',
|
type: 'whale',
|
||||||
description: 'Whales can move twice as quickly through this physics-defying liquid',
|
description: 'Whales can move twice as quickly through this physics-defying liquid',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 6_000_000
|
cost: 6_000_000
|
||||||
}),
|
}),
|
||||||
blightWhales: evil({
|
blightWhales: evil({
|
||||||
|
name: 'Blight Whales',
|
||||||
type: 'whale',
|
type: 'whale',
|
||||||
description: `Infectious with evil, they swim the ocean spreading their spores.`,
|
description: `Infectious with evil, they swim the ocean spreading their spores.`,
|
||||||
cost: 24_000_000
|
cost: 24_000_000
|
||||||
}),
|
}),
|
||||||
whaleChoir: heavenly({
|
whaleChoir: heavenly({
|
||||||
|
name: 'Whale Choir',
|
||||||
type: 'whale',
|
type: 'whale',
|
||||||
description: `Their cleansing songs reverberate through the sea. 8x Whale CPS.`,
|
description: `Their cleansing songs reverberate through the sea. 8x Whale CPS.`,
|
||||||
cost: 144_000_000,
|
cost: 144_000_000,
|
||||||
multiplier: 8,
|
multiplier: 8,
|
||||||
}),
|
}),
|
||||||
smolWhales: baby({
|
smolWhales: baby({
|
||||||
|
name: 'Smol Whales',
|
||||||
type: 'whale',
|
type: 'whale',
|
||||||
description: ``,
|
description: ``,
|
||||||
cost: 8_400_000_000
|
cost: 8_400_000_000
|
||||||
}),
|
}),
|
||||||
|
|
||||||
greasyTracks: basic({
|
greasyTracks: basic({
|
||||||
|
name: 'Greasy Tracks',
|
||||||
type: 'train',
|
type: 'train',
|
||||||
description: 'Lets trains deliver HVAC twice as efficiently',
|
description: 'Lets trains deliver HVAC twice as efficiently',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 1_300_000
|
cost: 1_300_000
|
||||||
}),
|
}),
|
||||||
rocketThrusters: basic({
|
rocketThrusters: basic({
|
||||||
|
name: 'Rocket Thrusters',
|
||||||
type: 'train',
|
type: 'train',
|
||||||
description: 'That\'ll put some quack on your track',
|
description: 'That\'ll put some quack on your track',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 22_000_000
|
cost: 22_000_000
|
||||||
}),
|
}),
|
||||||
loudConductors: basic({
|
loudConductors: basic({
|
||||||
|
name: 'Loud Conductors',
|
||||||
type: 'train',
|
type: 'train',
|
||||||
description: 'Conductors can onboard twice as much HVAC',
|
description: 'Conductors can onboard twice as much HVAC',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 65_000_000
|
cost: 65_000_000
|
||||||
}),
|
}),
|
||||||
hellTrain: evil({
|
hellTrain: evil({
|
||||||
|
name: 'Hell Train',
|
||||||
type: 'train',
|
type: 'train',
|
||||||
description: 'Shipping blood needed for the ritual.',
|
description: 'Shipping blood needed for the ritual.',
|
||||||
cost: 370_000_000
|
cost: 370_000_000
|
||||||
}),
|
}),
|
||||||
toyTrain: heavenly({
|
toyTrain: heavenly({
|
||||||
|
name: 'Toy Train',
|
||||||
type: 'train',
|
type: 'train',
|
||||||
description: 'Toot toot! 8x Train CPS.',
|
description: 'Toot toot! 8x Train CPS.',
|
||||||
multiplier: 8,
|
multiplier: 8,
|
||||||
|
@ -185,64 +216,75 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
gasolineFire: basic({
|
gasolineFire: basic({
|
||||||
|
name: 'Gasoline Fire',
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: 'Extremely good for breathing in.',
|
description: 'Extremely good for breathing in.',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 14_000_000
|
cost: 14_000_000
|
||||||
}),
|
}),
|
||||||
extremelyDryFuel: basic({
|
extremelyDryFuel: basic({
|
||||||
|
name: 'Extremely Dry Fuel',
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: 'Hey, psst, hey. Use the ignite command for a secret achievement.',
|
description: 'Hey, psst, hey. Use the ignite command for a secret achievement.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 163_000_000
|
cost: 163_000_000
|
||||||
}),
|
}),
|
||||||
cavemanFire: basic({
|
cavemanFire: basic({
|
||||||
|
name: 'Caveman Fire',
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: 'They just don\'t make \'em like they used to.',
|
description: 'They just don\'t make \'em like they used to.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 700_000_000
|
cost: 700_000_000
|
||||||
}),
|
}),
|
||||||
lava: evil({
|
lava: evil({
|
||||||
|
name: 'Lava',
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: `Hopefully no usurpers have any "accidents".`,
|
description: `Hopefully no usurpers have any "accidents".`,
|
||||||
cost: 4_200_000_000
|
cost: 4_200_000_000
|
||||||
}),
|
}),
|
||||||
blueFire: heavenly({
|
blueFire: heavenly({
|
||||||
|
name: 'Blue Fire',
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: `You can hear it singing with delight. 7x Fire CPS.`,
|
description: `You can hear it singing with delight. 7x Fire CPS.`,
|
||||||
multiplier: 7,
|
multiplier: 7,
|
||||||
cost: 25_200_000_000
|
cost: 25_200_000_000
|
||||||
}),
|
}),
|
||||||
cuteFire: baby({
|
cuteFire: baby({
|
||||||
|
name: 'Cute Fire',
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: `I just met my perfect match...`,
|
description: `I just met my perfect match...`,
|
||||||
cost: 150_000_000_000
|
cost: 150_000_000_000
|
||||||
}),
|
}),
|
||||||
|
|
||||||
spoonerang: basic({
|
spoonerang: basic({
|
||||||
|
name: 'Spoonerang',
|
||||||
type: 'boomerang',
|
type: 'boomerang',
|
||||||
description: 'Scoops up HVAC mid-flight',
|
description: 'Scoops up HVAC mid-flight',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 200_000_000
|
cost: 200_000_000
|
||||||
}),
|
}),
|
||||||
boomerAng: basic({
|
boomerAng: basic({
|
||||||
|
name: 'Boomer-ang',
|
||||||
type: 'boomerang',
|
type: 'boomerang',
|
||||||
description: 'It\'s... old.',
|
description: 'It\'s... old.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 1_200_000_000
|
cost: 1_200_000_000
|
||||||
}),
|
}),
|
||||||
doubleRang: basic({
|
doubleRang: basic({
|
||||||
|
name: 'Double-rang',
|
||||||
type: 'boomerang',
|
type: 'boomerang',
|
||||||
description: 'You throw one, but somehow catch two',
|
description: 'You throw one, but somehow catch two',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 10_000_000_000
|
cost: 10_000_000_000
|
||||||
}),
|
}),
|
||||||
loyalRang: evil({
|
loyalRang: evil({
|
||||||
|
name: 'Loyal-rang',
|
||||||
type: 'boomerang',
|
type: 'boomerang',
|
||||||
description: `Frequently reports back to your throne on the state of your empire.`,
|
description: `Frequently reports back to your throne on the state of your empire.`,
|
||||||
cost: 60_000_000_000
|
cost: 60_000_000_000
|
||||||
}),
|
}),
|
||||||
youRang: heavenly({
|
youRang: heavenly({
|
||||||
|
name: 'You-rang',
|
||||||
type: 'boomerang',
|
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.',
|
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,
|
multiplier: 7,
|
||||||
|
@ -250,29 +292,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lunarPower: basic({
|
lunarPower: basic({
|
||||||
|
name: 'Lunar Power',
|
||||||
type: 'moon',
|
type: 'moon',
|
||||||
description: 'Out with the sol, in with the lun!',
|
description: 'Out with the sol, in with the lun!',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 3_300_000_000
|
cost: 3_300_000_000
|
||||||
}),
|
}),
|
||||||
womanOnTheMoon: basic({
|
womanOnTheMoon: basic({
|
||||||
|
name: 'Woman on the Moon',
|
||||||
type: 'moon',
|
type: 'moon',
|
||||||
description: 'There\'s no reason for it not to be a woman!',
|
description: 'There\'s no reason for it not to be a woman!',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 39_700_000_000
|
cost: 39_700_000_000
|
||||||
}),
|
}),
|
||||||
doubleCraters: basic({
|
doubleCraters: basic({
|
||||||
|
name: 'Double-Craters',
|
||||||
type: 'moon',
|
type: 'moon',
|
||||||
description: 'Making every side look like the dark side.',
|
description: 'Making every side look like the dark side.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 165_000_000_000
|
cost: 165_000_000_000
|
||||||
}),
|
}),
|
||||||
tidalUpheaval: evil({
|
tidalUpheaval: evil({
|
||||||
|
name: 'Tidal Upheaval',
|
||||||
type: 'moon',
|
type: 'moon',
|
||||||
description: `The hell with the ocean. That's valuable_ *the abstract concept of more power* _we're losing.`,
|
description: `The hell with the ocean. That's valuable_ *the abstract concept of more power* _we're losing.`,
|
||||||
cost: 865_000_000_000
|
cost: 865_000_000_000
|
||||||
}),
|
}),
|
||||||
newMoon: heavenly({
|
newMoon: heavenly({
|
||||||
|
name: 'New Moon',
|
||||||
type: 'moon',
|
type: 'moon',
|
||||||
description: `Build a second moon to provide space for affordable housing. 6x Moon CPS.`,
|
description: `Build a second moon to provide space for affordable housing. 6x Moon CPS.`,
|
||||||
multiplier: 6,
|
multiplier: 6,
|
||||||
|
@ -280,29 +327,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
glassButterfly: basic({
|
glassButterfly: basic({
|
||||||
|
name: 'Glass Butterfly',
|
||||||
type: 'butterfly',
|
type: 'butterfly',
|
||||||
description: 'Not your grandma\'s universe manipulation.',
|
description: 'Not your grandma\'s universe manipulation.',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 51_000_000_000
|
cost: 51_000_000_000
|
||||||
}),
|
}),
|
||||||
monarchMigration: basic({
|
monarchMigration: basic({
|
||||||
|
name: 'Monarch Migration',
|
||||||
type: 'butterfly',
|
type: 'butterfly',
|
||||||
description: 'This upgrade brought to you by milkweed.',
|
description: 'This upgrade brought to you by milkweed.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 870_000_000_000
|
cost: 870_000_000_000
|
||||||
}),
|
}),
|
||||||
quadWing: basic({
|
quadWing: basic({
|
||||||
|
name: 'Quad-Wing',
|
||||||
type: 'butterfly',
|
type: 'butterfly',
|
||||||
description: 'Sounds a lot like a trillion bees buzzing inside your head.',
|
description: 'Sounds a lot like a trillion bees buzzing inside your head.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 2_550_000_000_000
|
cost: 2_550_000_000_000
|
||||||
}),
|
}),
|
||||||
venomousMoths: evil({
|
venomousMoths: evil({
|
||||||
|
name: 'Venomous Moths',
|
||||||
type: 'butterfly',
|
type: 'butterfly',
|
||||||
description: 'Specifically manufactured for their horrifying brain-melt toxins.',
|
description: 'Specifically manufactured for their horrifying brain-melt toxins.',
|
||||||
cost: 12_550_000_000_000
|
cost: 12_550_000_000_000
|
||||||
}),
|
}),
|
||||||
quietingNectar: heavenly({
|
quietingNectar: heavenly({
|
||||||
|
name: 'Quieting Nectar',
|
||||||
type: 'butterfly',
|
type: 'butterfly',
|
||||||
description: 'Calming and extra sweet. Soothes even human ails. 6x Butterfly CPS.',
|
description: 'Calming and extra sweet. Soothes even human ails. 6x Butterfly CPS.',
|
||||||
multiplier: 6,
|
multiplier: 6,
|
||||||
|
@ -310,29 +362,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
silverMirror: basic({
|
silverMirror: basic({
|
||||||
|
name: 'Silver Mirror',
|
||||||
type: 'mirror',
|
type: 'mirror',
|
||||||
description: 'Excellent for stabbing vampires.',
|
description: 'Excellent for stabbing vampires.',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 750_000_000_000
|
cost: 750_000_000_000
|
||||||
}),
|
}),
|
||||||
pocketMirror: basic({
|
pocketMirror: basic({
|
||||||
|
name: 'Pocket Mirror',
|
||||||
type: 'mirror',
|
type: 'mirror',
|
||||||
description: 'Take your self-reflection on the go!',
|
description: 'Take your self-reflection on the go!',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 18_000_000_000_000
|
cost: 18_000_000_000_000
|
||||||
}),
|
}),
|
||||||
window: basic({
|
window: basic({
|
||||||
|
name: 'Window',
|
||||||
type: 'mirror',
|
type: 'mirror',
|
||||||
description: 'Only through looking around you can you acquire the self reflection necessary to control the thermostat.',
|
description: 'Only through looking around you can you acquire the self reflection necessary to control the thermostat.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 37_500_000_000_000
|
cost: 37_500_000_000_000
|
||||||
}),
|
}),
|
||||||
crackedMirror: evil({
|
crackedMirror: evil({
|
||||||
|
name: 'Cracked Mirror',
|
||||||
type: 'mirror',
|
type: 'mirror',
|
||||||
description: `YOU SMILE. DO NOT FEAR, THIS IS THE FACE OF A FRIEND.`,
|
description: `YOU SMILE. DO NOT FEAR, THIS IS THE FACE OF A FRIEND.`,
|
||||||
cost: 222_000_000_000_000
|
cost: 222_000_000_000_000
|
||||||
}),
|
}),
|
||||||
funHouseMirror: heavenly({
|
funHouseMirror: heavenly({
|
||||||
|
name: 'Fun-House Mirror',
|
||||||
type: 'mirror',
|
type: 'mirror',
|
||||||
description: `yoU LOok so siLLY IN thesE THINgs. 5X mIRror CpS.`,
|
description: `yoU LOok so siLLY IN thesE THINgs. 5X mIRror CpS.`,
|
||||||
multiplier: 5,
|
multiplier: 5,
|
||||||
|
@ -340,29 +397,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fzero: basic({
|
fzero: basic({
|
||||||
|
name: 'F-Zero',
|
||||||
type: 'quade',
|
type: 'quade',
|
||||||
description: 'Brings out his competitive spirit.',
|
description: 'Brings out his competitive spirit.',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 10_000_000_000_000
|
cost: 10_000_000_000_000
|
||||||
}),
|
}),
|
||||||
triHumpCamel: basic({
|
triHumpCamel: basic({
|
||||||
|
name: 'Trimedary Camel',
|
||||||
type: 'quade',
|
type: 'quade',
|
||||||
description: 'YEE HAW :trimedary_camel:',
|
description: 'YEE HAW :trimedary_camel:',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 200_000_000_000_000
|
cost: 200_000_000_000_000
|
||||||
}),
|
}),
|
||||||
adam: basic({
|
adam: basic({
|
||||||
|
name: 'Adam',
|
||||||
type: 'quade',
|
type: 'quade',
|
||||||
description: 'He could probably reach the thermostat if he wanted.',
|
description: 'He could probably reach the thermostat if he wanted.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 500_000_000_000_000
|
cost: 500_000_000_000_000
|
||||||
}),
|
}),
|
||||||
thatsNotQuade: evil({
|
thatsNotQuade: evil({
|
||||||
|
name: `That's not Quade...`,
|
||||||
type: 'quade',
|
type: 'quade',
|
||||||
description: `The skinless face lacks even a moustache. Nevertheless, it pledges its allegiance.`,
|
description: `The skinless face lacks even a moustache. Nevertheless, it pledges its allegiance.`,
|
||||||
cost: 3_000_000_000_000_000
|
cost: 3_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
hannahMontanaLinux: heavenly({
|
hannahMontanaLinux: heavenly({
|
||||||
|
name: 'Hannah Montana Linux',
|
||||||
type: 'quade',
|
type: 'quade',
|
||||||
description: `The patrician's choice. 4x Quade CPS.`,
|
description: `The patrician's choice. 4x Quade CPS.`,
|
||||||
multiplier: 4,
|
multiplier: 4,
|
||||||
|
@ -370,29 +432,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
latestNode: basic({
|
latestNode: basic({
|
||||||
|
name: 'Latest Node',
|
||||||
type: 'hvacker',
|
type: 'hvacker',
|
||||||
description: 'The old one has terrible ergonomics, tsk tsk.',
|
description: 'The old one has terrible ergonomics, tsk tsk.',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 140_000_000_000_000
|
cost: 140_000_000_000_000
|
||||||
}),
|
}),
|
||||||
nativeFunctions: basic({
|
nativeFunctions: basic({
|
||||||
|
name: 'Native Functions',
|
||||||
type: 'hvacker',
|
type: 'hvacker',
|
||||||
description: 'Sometimes javascript just isn\'t fast enough.',
|
description: 'Sometimes javascript just isn\'t fast enough.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 3_300_000_000_000_000
|
cost: 3_300_000_000_000_000
|
||||||
}),
|
}),
|
||||||
gitCommits: basic({
|
gitCommits: basic({
|
||||||
|
name: 'Git Commits',
|
||||||
type: 'hvacker',
|
type: 'hvacker',
|
||||||
description: 'The heads of multiple people in a company are better than, for example, merely one head.',
|
description: 'The heads of multiple people in a company are better than, for example, merely one head.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 7_000_000_000_000_000
|
cost: 7_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
undefinedBehavior: evil({
|
undefinedBehavior: evil({
|
||||||
|
name: 'Undefined Behavior',
|
||||||
type: 'hvacker',
|
type: 'hvacker',
|
||||||
description: `skREEEFDS☐☐☐☐☐it's☐jwtoo☐laate☐☐☐☐☐`,
|
description: `skREEEFDS☐☐☐☐☐it's☐jwtoo☐laate☐☐☐☐☐`,
|
||||||
cost: 42_000_000_000_000_000
|
cost: 42_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
mutualUnderstanding: heavenly({
|
mutualUnderstanding: heavenly({
|
||||||
|
name: 'Mutual Understanding',
|
||||||
type: 'hvacker',
|
type: 'hvacker',
|
||||||
description: `lol fat chance, dummy. Points for trying, though. 3x Hvacker CPS`,
|
description: `lol fat chance, dummy. Points for trying, though. 3x Hvacker CPS`,
|
||||||
multiplier: 3,
|
multiplier: 3,
|
||||||
|
@ -400,29 +467,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
coffee: basic({
|
coffee: basic({
|
||||||
|
name: 'Coffee',
|
||||||
type: 'creator',
|
type: 'creator',
|
||||||
description: `Didn't you know? It makes you smarter. No consequencAAAAAA`,
|
description: `Didn't you know? It makes you smarter. No consequencAAAAAA`,
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 1_960_000_000_000_000
|
cost: 1_960_000_000_000_000
|
||||||
}),
|
}),
|
||||||
bribery: basic({
|
bribery: basic({
|
||||||
|
name: 'Bribery',
|
||||||
type: 'creator',
|
type: 'creator',
|
||||||
description: `How much could he be making that a couple bucks won't get me more HVAC?`,
|
description: `How much could he be making that a couple bucks won't get me more HVAC?`,
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 32_300_000_000_000_000
|
cost: 32_300_000_000_000_000
|
||||||
}),
|
}),
|
||||||
vim: basic({
|
vim: basic({
|
||||||
|
name: 'Vim',
|
||||||
type: 'creator',
|
type: 'creator',
|
||||||
description: `*teleports behind you*`,
|
description: `*teleports behind you*`,
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 100_000_000_000_000_000
|
cost: 100_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
regrets: evil({
|
regrets: evil({
|
||||||
|
name: 'Regrets',
|
||||||
type: 'creator',
|
type: 'creator',
|
||||||
description: `HE HAS NONE. HE LAUGHS.`,
|
description: `HE HAS NONE. HE LAUGHS.`,
|
||||||
cost: 600_000_000_000_000_000
|
cost: 600_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
goVegan: heavenly({
|
goVegan: heavenly({
|
||||||
|
name: 'Go Vegan',
|
||||||
type: 'creator',
|
type: 'creator',
|
||||||
description: `Unlock your vegan powers. 3x Creator CPS.`,
|
description: `Unlock your vegan powers. 3x Creator CPS.`,
|
||||||
multiplier: 3,
|
multiplier: 3,
|
||||||
|
@ -430,29 +502,34 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
angelInvestors: basic({
|
angelInvestors: basic({
|
||||||
|
name: 'Angel Investors',
|
||||||
type: 'smallBusiness',
|
type: 'smallBusiness',
|
||||||
description: 'Not so small NOW are we?',
|
description: 'Not so small NOW are we?',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 3_140_000_000_000_000
|
cost: 3_140_000_000_000_000
|
||||||
}),
|
}),
|
||||||
officeManager: basic({
|
officeManager: basic({
|
||||||
|
name: 'Office Manager',
|
||||||
type: 'smallBusiness',
|
type: 'smallBusiness',
|
||||||
description: 'Sate your laborers with snacks.',
|
description: 'Sate your laborers with snacks.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 80_000_000_000_000_000
|
cost: 80_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
undyingLoyalty: basic({
|
undyingLoyalty: basic({
|
||||||
|
name: 'Undying Loyalty',
|
||||||
type: 'smallBusiness',
|
type: 'smallBusiness',
|
||||||
description: 'Your foolish employees bow to your every whim, regardless of salary.',
|
description: 'Your foolish employees bow to your every whim, regardless of salary.',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 138_000_000_000_000_000
|
cost: 138_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
deathSquad: evil({
|
deathSquad: evil({
|
||||||
|
name: 'Death Squad',
|
||||||
type: 'smallBusiness',
|
type: 'smallBusiness',
|
||||||
description: `pwease don't unionize uwu :pleading_face:`,
|
description: `pwease don't unionize uwu :pleading_face:`,
|
||||||
cost: 858_000_000_000_000_000
|
cost: 858_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
coop: heavenly({
|
coop: heavenly({
|
||||||
|
name: 'Co-Op',
|
||||||
type: 'smallBusiness',
|
type: 'smallBusiness',
|
||||||
description: `By the people, for the people. 2x smallBusiness CPS`,
|
description: `By the people, for the people. 2x smallBusiness CPS`,
|
||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
|
@ -460,36 +537,77 @@ module.exports = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
corporateBuyouts: basic({
|
corporateBuyouts: basic({
|
||||||
|
name: 'Corporate Buyouts',
|
||||||
type: 'bigBusiness',
|
type: 'bigBusiness',
|
||||||
description: 'The cornerstone of any family-run business.',
|
description: 'The cornerstone of any family-run business.',
|
||||||
count: 1,
|
count: 1,
|
||||||
cost: 28_140_000_000_000_000
|
cost: 28_140_000_000_000_000
|
||||||
}),
|
}),
|
||||||
politicalSway: basic({
|
politicalSway: basic({
|
||||||
|
name: 'Political Sway',
|
||||||
type: 'bigBusiness',
|
type: 'bigBusiness',
|
||||||
description: `What's a bit of lobbying between friends?`,
|
description: `What's a bit of lobbying between friends?`,
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 560_000_000_000_000_000
|
cost: 560_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
humanDiscontent: basic({
|
humanDiscontent: basic({
|
||||||
|
name: 'Human Discontent',
|
||||||
type: 'bigBusiness',
|
type: 'bigBusiness',
|
||||||
description: 'A sad populace is a spendy populace!',
|
description: 'A sad populace is a spendy populace!',
|
||||||
count: 25,
|
count: 25,
|
||||||
cost: 1_372_000_000_000_000_000
|
cost: 1_372_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
weJustKillPeopleNow: evil({
|
weJustKillPeopleNow: evil({
|
||||||
|
name: 'We Just Kill People Now',
|
||||||
type: 'bigBusiness',
|
type: 'bigBusiness',
|
||||||
description: 'It is extremely difficult to get more evil than we already were. Nevertheless,',
|
description: 'It is extremely difficult to get more evil than we already were. Nevertheless,',
|
||||||
cost: 7_072_000_000_000_000_000
|
cost: 7_072_000_000_000_000_000
|
||||||
}),
|
}),
|
||||||
makePublic: heavenly({
|
makePublic: heavenly({
|
||||||
|
name: 'Make Public',
|
||||||
type: 'bigBusiness',
|
type: 'bigBusiness',
|
||||||
description: `Downplay immediate profit for more long-term benefits. 2x bigBusiness CPS.`,
|
description: `Downplay immediate profit for more long-term benefits. 2x bigBusiness CPS.`,
|
||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
cost: 42_000_000_000_000_000_000
|
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: {
|
homage: {
|
||||||
|
name: 'Homage',
|
||||||
type: 'general',
|
type: 'general',
|
||||||
description: 'The power of original ideas increases your overall CPS by 10%',
|
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,
|
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
|
effect: (itemCps, user) => itemCps * 1.1
|
||||||
},
|
},
|
||||||
iLoveHvac: {
|
iLoveHvac: {
|
||||||
|
name: 'iLoveHvac',
|
||||||
type: 'general',
|
type: 'general',
|
||||||
description: 'The power of love increases your overall CPS by 10%',
|
description: 'The power of love increases your overall CPS by 10%',
|
||||||
condition: user => Object.entries(user.items).reduce((total, [, countOwned]) => countOwned + total, 0) >= 400,
|
condition: user => Object.entries(user.items).reduce((total, [, countOwned]) => countOwned + total, 0) >= 400,
|
||||||
emoji: 'heart',
|
emoji: 'heart',
|
||||||
cost: 100_000_000_000_000,
|
cost: 100_000_000_000_000,
|
||||||
effect: (itemCps, user) => itemCps * 1.1
|
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 jokes = require('../jokes')
|
||||||
const achievements = require('./achievements')
|
const achievements = require('./achievements')
|
||||||
const buyableItems = require('./buyableItems')
|
const buyableItems = require('./buyableItems')
|
||||||
const upgrades = require('./upgrades')
|
|
||||||
const { quackStore, getChaos } = require('./quackstore')
|
const { quackStore, getChaos } = require('./quackstore')
|
||||||
|
|
||||||
|
let upgrades
|
||||||
|
const setUpgrades = upg => {
|
||||||
|
upgrades = upg
|
||||||
|
}
|
||||||
|
|
||||||
const saveFile = 'hvacoins.json'
|
const saveFile = 'hvacoins.json'
|
||||||
|
|
||||||
const logError = msg => msg ? console.error('logError: ', msg) : () => { /* Don't log empty message */ }
|
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
|
return chaosed
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseOr = (parseable, orFunc) => {
|
const parseOr = (parseable, fallback) => {
|
||||||
try {
|
try {
|
||||||
|
if (typeof parseable === 'function') {
|
||||||
|
parseable = parseable()
|
||||||
|
}
|
||||||
return JSON.parse(parseable)
|
return JSON.parse(parseable)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e)
|
logError(e)
|
||||||
return orFunc()
|
return fallback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,13 +60,18 @@ const makeBackup = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let saves = 0
|
let saves = 0
|
||||||
const saveGame = (force = true) => {
|
const saveGame = (after, force = true) => {
|
||||||
if (saves % 100 === 0) {
|
if (saves % 20 === 0) {
|
||||||
makeBackup()
|
makeBackup()
|
||||||
}
|
}
|
||||||
saves += 1
|
saves += 1
|
||||||
if (force || saves % 10 === 0) {
|
if (force || saves % 10 === 0) {
|
||||||
|
if (after) {
|
||||||
|
console.log(`SAVING GAME after ${after}`)
|
||||||
|
} else {
|
||||||
console.log('SAVING GAME')
|
console.log('SAVING GAME')
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync('./' + saveFile, JSON.stringify(game, null, 2))
|
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],
|
['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],
|
['nonillion', 1_000_000_000_000_000_000_000_000_000_000],
|
||||||
['octillion', 1_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],
|
['sextillion', 1_000_000_000_000_000_000_000],
|
||||||
['quintillion', 1_000_000_000_000_000_000],
|
['quintillion', 1_000_000_000_000_000_000],
|
||||||
['quadrillion', 1_000_000_000_000_000],
|
['quadrillion', 1_000_000_000_000_000],
|
||||||
|
@ -101,18 +113,24 @@ const bigNumberWords = [
|
||||||
['million', 1_000_000],
|
['million', 1_000_000],
|
||||||
]
|
]
|
||||||
|
|
||||||
const commas = (num, precise = false) => {
|
const commas = (num, precise = false, skipWords = false) => {
|
||||||
num = Math.round(num)
|
num = Math.round(num)
|
||||||
|
if (num === 1) {
|
||||||
|
return 'one'
|
||||||
|
}
|
||||||
const bigNum = bigNumberWords.find(([, base]) => num >= base)
|
const bigNum = bigNumberWords.find(([, base]) => num >= base)
|
||||||
if (bigNum && !precise) {
|
if (bigNum && !precise) {
|
||||||
const [name, base] = bigNum
|
const [name, base] = bigNum
|
||||||
const nummed = (num / base).toPrecision(3)
|
const nummed = (num / base).toPrecision(3)
|
||||||
|
if (skipWords) {
|
||||||
|
return nummed
|
||||||
|
}
|
||||||
return `${nummed} ${name}`
|
return `${nummed} ${name}`
|
||||||
}
|
}
|
||||||
return num.toLocaleString()
|
return num.toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseAll = (str, allNum) => {
|
const parseAll = (str, allNum, user) => {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return NaN
|
return NaN
|
||||||
}
|
}
|
||||||
|
@ -144,11 +162,21 @@ const parseAll = (str, allNum) => {
|
||||||
case 'one hunna':
|
case 'one hunna':
|
||||||
return 100
|
return 100
|
||||||
}
|
}
|
||||||
|
if (user && buyableItems[str]) {
|
||||||
|
return calculateCost({ itemName: str, user, quantity: 1 })
|
||||||
|
}
|
||||||
|
|
||||||
console.log('STR', str)
|
console.log('STR', str)
|
||||||
if (str.match(/^\d+$/)) {
|
if (str.match(/^\d+$/)) {
|
||||||
return parseInt(str)
|
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+$/)) {
|
if (str.match(/^\d+\.\d+$/)) {
|
||||||
return Math.round(parseFloat(str))
|
return Math.round(parseFloat(str))
|
||||||
|
@ -162,6 +190,16 @@ const parseAll = (str, allNum) => {
|
||||||
return NaN
|
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 game = loadGame()
|
||||||
const { users, nfts, squad } = game
|
const { users, nfts, squad } = game
|
||||||
|
|
||||||
|
@ -182,7 +220,7 @@ const addAchievement = (user, achievementName, say) => {
|
||||||
}
|
}
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
user.achievements[achievementName] = true
|
user.achievements[achievementName] = true
|
||||||
saveGame()
|
saveGame(`${user.name} earned ${achievementName}`)
|
||||||
await say(`You earned the achievement ${achievements[achievementName].name}!`)
|
await say(`You earned the achievement ${achievements[achievementName].name}!`)
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
@ -199,22 +237,17 @@ const getIdFromName = name => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUser = userId => {
|
const getUser = (userId, updateCoins = false) => {
|
||||||
if (!users[userId]) {
|
users[userId] ??= {}
|
||||||
users[userId] = {
|
users[userId].coins ??= 0
|
||||||
coins: 0,
|
|
||||||
items: {},
|
|
||||||
upgrades: {},
|
|
||||||
achievements: {},
|
|
||||||
coinsAllTime: 0,
|
|
||||||
prestige: 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
users[userId].items ??= {}
|
users[userId].items ??= {}
|
||||||
users[userId].upgrades ??= {}
|
users[userId].upgrades ??= {}
|
||||||
users[userId].achievements ??= {}
|
users[userId].achievements ??= {}
|
||||||
users[userId].coinsAllTime ??= users[userId].coins
|
users[userId].coinsAllTime ??= users[userId].coins
|
||||||
users[userId].prestige ??= 0
|
users[userId].prestige ??= 0
|
||||||
|
users[userId].startDate ??= new Date()
|
||||||
|
if (updateCoins) {
|
||||||
|
users[userId].coins = getCoins(userId)
|
||||||
}
|
}
|
||||||
return users[userId]
|
return users[userId]
|
||||||
}
|
}
|
||||||
|
@ -251,6 +284,7 @@ const squadUpgrades = {
|
||||||
tastyKeyboards: {
|
tastyKeyboards: {
|
||||||
name: 'Tasty Keyboards',
|
name: 'Tasty Keyboards',
|
||||||
description: 'Delicious and sticky. Boosts CPS by 20% for everyone.',
|
description: 'Delicious and sticky. Boosts CPS by 20% for everyone.',
|
||||||
|
|
||||||
effect: cps => cps * 1.2,
|
effect: cps => cps * 1.2,
|
||||||
cost: 10_000_000_000_000,
|
cost: 10_000_000_000_000,
|
||||||
emoji: 'keyboard'
|
emoji: 'keyboard'
|
||||||
|
@ -298,6 +332,11 @@ const quackGradeMultiplier = user => {
|
||||||
return userQuackgrades.reduce((total, upgrade) => quackStore[upgrade].effect(total, user), 1)
|
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 singleItemCps = (user, itemName) => {
|
||||||
const baseCps = buyableItems[itemName].earning
|
const baseCps = buyableItems[itemName].earning
|
||||||
// console.log('')
|
// console.log('')
|
||||||
|
@ -326,6 +365,9 @@ const singleItemCps = (user, itemName) => {
|
||||||
const squadGradeMultiplier = getCompletedSquadgrades().reduce((cps, upgrade) => upgrade.effect(cps), 1)
|
const squadGradeMultiplier = getCompletedSquadgrades().reduce((cps, upgrade) => upgrade.effect(cps), 1)
|
||||||
// console.log('squadGradeMultiplier', squadGradeMultiplier)
|
// console.log('squadGradeMultiplier', squadGradeMultiplier)
|
||||||
|
|
||||||
|
const petMultiplier = petQuackGradeMultiplier(user)
|
||||||
|
//console.log('petMultiplier', petMultiplier)
|
||||||
|
|
||||||
const total =
|
const total =
|
||||||
baseCps *
|
baseCps *
|
||||||
achievementMultiplier *
|
achievementMultiplier *
|
||||||
|
@ -333,7 +375,8 @@ const singleItemCps = (user, itemName) => {
|
||||||
generalUpgradeCps *
|
generalUpgradeCps *
|
||||||
quackGrade *
|
quackGrade *
|
||||||
pMult *
|
pMult *
|
||||||
squadGradeMultiplier
|
squadGradeMultiplier *
|
||||||
|
petMultiplier
|
||||||
|
|
||||||
// console.log('Single Item CPS:', total)
|
// console.log('Single Item CPS:', total)
|
||||||
|
|
||||||
|
@ -372,6 +415,7 @@ const definitelyShuffle = (str, percentOdds) => {
|
||||||
let shuffled = str
|
let shuffled = str
|
||||||
while (shuffled === str) {
|
while (shuffled === str) {
|
||||||
shuffled = shufflePercent(str, percentOdds)
|
shuffled = shufflePercent(str, percentOdds)
|
||||||
|
console.log('Shuffling... "' + shuffled + '"')
|
||||||
}
|
}
|
||||||
return shuffled
|
return shuffled
|
||||||
}
|
}
|
||||||
|
@ -436,6 +480,71 @@ game.stonkMarket ??= {
|
||||||
|
|
||||||
const userHasCheckedQuackgrade = (user, quackGrade) => (user.quackUpgrades?.checked || []).includes(quackGrade)
|
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 = {
|
module.exports = {
|
||||||
saveGame,
|
saveGame,
|
||||||
makeBackup,
|
makeBackup,
|
||||||
|
@ -468,5 +577,10 @@ module.exports = {
|
||||||
userHasCheckedQuackgrade,
|
userHasCheckedQuackgrade,
|
||||||
fuzzyMatcher,
|
fuzzyMatcher,
|
||||||
addCoins,
|
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 user = getUser(event.user)
|
||||||
const haunted = false
|
const haunted = false
|
||||||
//await action({ event, say, words, args, commandName })
|
//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) {
|
if (!canUse) {
|
||||||
await say(`Command '${words[0]}' not found`)
|
await say(`Command '${words[0]}' not found`)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,45 +1,63 @@
|
||||||
const slack = require('../slack')
|
const slack = require('../slack')
|
||||||
|
const { updateAll } = require('../games/hvacoins/utils')
|
||||||
|
|
||||||
const tie = 'TIE'
|
const tie = 'TIE'
|
||||||
|
|
||||||
const messageFromBoard = ({ dataName, gameName, textFromBoard, board, player1, player2 }) =>
|
const messageFromBoard = ({ dataName, gameName, textFromBoard, board, player1, player2, channelMap }) =>
|
||||||
gameName + ' between ' + player1.toUpperCase() + ' and ' + player2.toUpperCase() + ' ' + encodeGame(dataName, board, [player1, player2]) + '\n' +
|
gameName + ' between ' + player1.toUpperCase() + ' and ' + player2.toUpperCase() + ' ' + encodeGame(dataName, board, [player1, player2], channelMap) + '\n' +
|
||||||
'```' + textFromBoard(board) + '\n```'
|
'```' + textFromBoard(board) + '\n```'
|
||||||
|
|
||||||
const addChoiceEmojis = async ({ choices, channel, ts }) => {
|
const addChoiceEmojis = async ({ choices, channel, ts }) => {
|
||||||
const addEmoji = async emojiName =>
|
const addEmoji = async emojiName => {
|
||||||
|
try {
|
||||||
await slack.app.client.reactions.add({
|
await slack.app.client.reactions.add({
|
||||||
channel,
|
channel,
|
||||||
timestamp: ts,
|
timestamp: ts,
|
||||||
name: emojiName
|
name: emojiName
|
||||||
})
|
})
|
||||||
|
} catch (ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const choice of choices) {
|
for (const choice of choices) {
|
||||||
await addEmoji(choice)
|
await addEmoji(choice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildGameStarter = ({ startTriggers, dataName, gameName, textFromBoard, initialBoard, turnChoiceEmojis }) => async ({ event, say }) => {
|
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()
|
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')
|
console.log('Trigger found')
|
||||||
const opponent = event.text.toUpperCase().match(/<@[^>]*>/)[0]
|
const opponent = event.text.toUpperCase().match(/<@[^>]*>/)[0]
|
||||||
console.log('Messaging opponent ' + slack.users[opponent.substring(2, opponent.length - 1)])
|
console.log('Messaging opponent ' + slack.users[opponent.substring(2, opponent.length - 1)])
|
||||||
const msg = messageFromBoard({
|
const channelMap = {}
|
||||||
|
const msg = () => messageFromBoard({
|
||||||
dataName,
|
dataName,
|
||||||
gameName,
|
gameName,
|
||||||
textFromBoard,
|
textFromBoard,
|
||||||
board: initialBoard(),
|
board: initialBoard(),
|
||||||
player1: '<@' + event.user + '>',
|
player1: '<@' + event.user + '>',
|
||||||
player2: opponent
|
player2: opponent,
|
||||||
|
channelMap
|
||||||
})
|
})
|
||||||
const sent = await say(msg)
|
const sent = await say(msg())
|
||||||
await addChoiceEmojis({ ...sent, choices: turnChoiceEmojis })
|
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)
|
const decodeGame = (dataKey, message) => slack.decodeData(dataKey, message)
|
||||||
|
|
||||||
|
@ -68,7 +86,8 @@ const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turn
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { board, players } = game
|
game.channelMap ??= {}
|
||||||
|
const { board, players, channelMap } = game
|
||||||
let winner = checkWinner(board)
|
let winner = checkWinner(board)
|
||||||
if (winner) {
|
if (winner) {
|
||||||
console.log('winner found: ' + winner)
|
console.log('winner found: ' + winner)
|
||||||
|
@ -85,33 +104,58 @@ const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turn
|
||||||
}
|
}
|
||||||
winner = checkWinner(board)
|
winner = checkWinner(board)
|
||||||
|
|
||||||
const boardMessage = messageFromBoard({
|
const boardMessage = () => messageFromBoard({
|
||||||
dataName,
|
dataName,
|
||||||
gameName,
|
gameName,
|
||||||
textFromBoard,
|
textFromBoard,
|
||||||
board,
|
board,
|
||||||
player1,
|
player1,
|
||||||
player2
|
player2,
|
||||||
|
channelMap
|
||||||
})
|
})
|
||||||
const winnerMessages = getMessages(winner)
|
if (winner) {
|
||||||
await say(boardMessage + winnerMessages.you)
|
await updateAll({ name: gameName, text: boardMessage() + '\nSomebody won! I do not yet know who!' })
|
||||||
if (!winner) {
|
const removeEmoji = emojiName => Object.values(channelMap).forEach(({ channel, ts }) =>
|
||||||
await say('Waiting for opponent\'s response...')
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeEmoji = async emojiName =>
|
|
||||||
slack.app.client.reactions.remove({
|
slack.app.client.reactions.remove({
|
||||||
channel: event.item.channel,
|
channel,
|
||||||
timestamp: message.messages[0]?.ts,
|
timestamp: ts,
|
||||||
name: emojiName
|
name: emojiName
|
||||||
})
|
}))
|
||||||
turnChoiceEmojis.forEach(removeEmoji)
|
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)
|
console.log('SENDING to ' + opponent)
|
||||||
|
if (!channelMap[opponent]) {
|
||||||
const sentBoard = await slack.app.client.chat.postMessage({
|
const sentBoard = await slack.app.client.chat.postMessage({
|
||||||
channel: opponent,
|
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) {
|
if (!winner) {
|
||||||
|
const sentBoard = channelMap[opponent]
|
||||||
await addChoiceEmojis({ ...sentBoard, choices: turnChoiceEmojis })
|
await addChoiceEmojis({ ...sentBoard, choices: turnChoiceEmojis })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
const { App: SlackApp } = require('@slack/bolt')
|
const { App: SlackApp } = require('@slack/bolt')
|
||||||
const config = require('../config')
|
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 temperatureChannelId = 'C034156CE03'
|
||||||
|
const dailyStandupChannelId = 'C03L533AU3Z'
|
||||||
|
|
||||||
const pollingMinutes = 5
|
const pollingMinutes = 5
|
||||||
const pollingPeriod = 1000 * 60 * pollingMinutes
|
const pollingPeriod = 1000 * 60 * pollingMinutes
|
||||||
|
|
||||||
|
const MAX_POLLS = 3
|
||||||
|
const HOURS_PER_WINDOW = 2
|
||||||
|
|
||||||
const colderEmoji = 'snowflake'
|
const colderEmoji = 'snowflake'
|
||||||
const hotterEmoji = 'fire'
|
const hotterEmoji = 'fire'
|
||||||
const goodEmoji = '+1'
|
const goodEmoji = '+1'
|
||||||
|
|
||||||
let app
|
const app = new SlackApp({
|
||||||
try {
|
|
||||||
app = new SlackApp({
|
|
||||||
token: config.slackBotToken,
|
token: config.slackBotToken,
|
||||||
signingSecret: config.slackSigningSecret,
|
signingSecret: config.slackSigningSecret,
|
||||||
appToken: config.slackAppToken,
|
appToken: config.slackAppToken,
|
||||||
|
@ -23,9 +26,6 @@ try {
|
||||||
// temperatureChannelId = fetched.channels.filter(channel => channel.name === 'thermo-posting')[0].id
|
// temperatureChannelId = fetched.channels.filter(channel => channel.name === 'thermo-posting')[0].id
|
||||||
// console.log('techThermostatChannelId', temperatureChannelId)
|
// console.log('techThermostatChannelId', temperatureChannelId)
|
||||||
// })
|
// })
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to initialize SlackApp', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pollTriggers = ['!temp', '!temperature', '!imhot', '!imcold', '!imfreezing', '!idonthavemysweater']
|
const pollTriggers = ['!temp', '!temperature', '!imhot', '!imcold', '!imfreezing', '!idonthavemysweater']
|
||||||
const halfTriggers = ['change temperature', "i'm cold", "i'm hot", 'quack', 'hvacker', '<@U0344TFA7HQ>']
|
const halfTriggers = ['change temperature', "i'm cold", "i'm hot", 'quack', 'hvacker', '<@U0344TFA7HQ>']
|
||||||
|
@ -41,7 +41,7 @@ const sendHelp = async (say, prefix) => {
|
||||||
text: prefix +
|
text: prefix +
|
||||||
`Sending a message matching any of \`${pollTriggers.join('`, `')}\` will start a temperature poll.\n` +
|
`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' +
|
'\'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 = {
|
const users = parseOr(fs.readFileSync('./users.json', 'utf-8'),
|
||||||
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 buildSayPrepend = ({ say, prepend }) => async msg => {
|
const buildSayPrepend = ({ say, prepend }) => async msg => {
|
||||||
if (typeof(msg) === 'string') {
|
if (typeof(msg) === 'string') {
|
||||||
|
@ -99,10 +73,11 @@ const buildSayPrepend = ({ say, prepend }) => async msg => {
|
||||||
}
|
}
|
||||||
|
|
||||||
process.once('SIGINT', code => {
|
process.once('SIGINT', code => {
|
||||||
saveGame(true)
|
saveGame(null, true)
|
||||||
process.exit()
|
process.exit()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let pollHistory = []
|
||||||
const activePolls = {}
|
const activePolls = {}
|
||||||
const testId = 'U028BMEBWBV_TEST'
|
const testId = 'U028BMEBWBV_TEST'
|
||||||
let testMode = false
|
let testMode = false
|
||||||
|
@ -113,18 +88,18 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
userName: users[event.user]
|
userName: users[event.user]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (event?.user === users.Sage) {
|
if (event?.user === users.Admin) {
|
||||||
if (event?.text.startsWith('!')) {
|
if (event?.text.startsWith('!')) {
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
await messageSage('Currently in test mode!')
|
await messageAdmin('Currently in test mode!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event?.text === '!test') {
|
if (event?.text === '!test') {
|
||||||
testMode = !testMode
|
testMode = !testMode
|
||||||
await messageSage(`TestMode: ${testMode} with ID ${testId}`)
|
await messageAdmin(`TestMode: ${testMode} with ID ${testId}`)
|
||||||
} else if (event?.text === '!notest') {
|
} else if (event?.text === '!notest') {
|
||||||
testMode = false
|
testMode = false
|
||||||
await messageSage(`TestMode: ${testMode}`)
|
await messageAdmin(`TestMode: ${testMode}`)
|
||||||
}
|
}
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
event.user = testId
|
event.user = testId
|
||||||
|
@ -136,16 +111,21 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
if (event.user) {
|
if (event.user) {
|
||||||
console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
|
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') {
|
if (event.text === '!!kill') {
|
||||||
saveGame(true)
|
saveGame(null, true)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
} else if (event.text === '!!restart') {
|
} else if (event.text === '!!restart') {
|
||||||
saveGame(true)
|
if (Object.entries(activePolls).length === 0) {
|
||||||
process.exit()
|
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')) {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,17 +136,37 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pollTriggers.includes(eventText)) {
|
if (!pollTriggers.includes(eventText) || event.user === users.John) {
|
||||||
if (halfTriggers.includes(eventText)) {
|
if (halfTriggers.includes(eventText)) {
|
||||||
await sendHelp(say, 'It looks like you might want to change the temperature.')
|
await sendHelp(say, 'It looks like you might want to change the temperature.')
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.channel !== temperatureChannelId) {
|
||||||
|
return say(`Please request polls in the appropriate channel.`)
|
||||||
|
}
|
||||||
|
|
||||||
if (activePolls[event.channel]) {
|
if (activePolls[event.channel]) {
|
||||||
await postToTechThermostatChannel({ text: "There's already an active poll in this channel!" })
|
await postToTechThermostatChannel({ text: "There's already an active poll in this channel!" })
|
||||||
return
|
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
|
activePolls[event.channel] = true
|
||||||
const pollTs = await startPoll()
|
const pollTs = await startPoll()
|
||||||
|
@ -215,11 +215,11 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
|
|
||||||
let text
|
let text
|
||||||
if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
|
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.'
|
text += 'raise the temperature, quack.'
|
||||||
requestTempChange('Hotter')
|
requestTempChange('Hotter')
|
||||||
} else if (colderVotes > hotterVotes && colderVotes > contentVotes) {
|
} 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.'
|
text += 'lower the temperature, quack quack.'
|
||||||
requestTempChange('Colder')
|
requestTempChange('Colder')
|
||||||
} else {
|
} else {
|
||||||
|
@ -230,11 +230,18 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
|
|
||||||
await postToTechThermostatChannel({ text })
|
await postToTechThermostatChannel({ text })
|
||||||
delete activePolls[event.channel]
|
delete activePolls[event.channel]
|
||||||
|
if (pendingRestart && Object.entries(activePolls).length === 0) {
|
||||||
|
await messageAdmin('Performing pending restart!')
|
||||||
|
saveGame(null, true)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
}, pollingPeriod)
|
}, pollingPeriod)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let pendingRestart = false
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await app.start().catch(console.error)
|
await app.start()
|
||||||
console.log('Slack Bolt has started')
|
console.log('Slack Bolt has started')
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
@ -247,7 +254,7 @@ const postToTechThermostatChannel = async optionsOrText => {
|
||||||
return app.client.chat.postMessage({ ...optionsOrText, channel: temperatureChannelId })
|
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) => {
|
const messageIn = async (channel, optionsOrText) => {
|
||||||
if (optionsOrText === null || typeof optionsOrText !== 'object') {
|
if (optionsOrText === null || typeof optionsOrText !== 'object') {
|
||||||
|
@ -262,7 +269,7 @@ const startPoll = async () => {
|
||||||
const sent = await postToTechThermostatChannel({
|
const sent = await postToTechThermostatChannel({
|
||||||
text: `<!here> Temperature poll requested! In ${pollingMinutes} minutes the temperature will be adjusted.\n` +
|
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.` +
|
`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({
|
await addReactions({
|
||||||
app,
|
app,
|
||||||
|
@ -281,10 +288,12 @@ const requestTempChange = change => {
|
||||||
tempChangeListeners.forEach(listener => listener(change))
|
tempChangeListeners.forEach(listener => listener(change))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noinspection HttpUrlsUsage
|
||||||
const encodeData = (key, data) =>
|
const encodeData = (key, data) =>
|
||||||
`<http://${key}ZZZ${Buffer.from(JSON.stringify(data), 'utf-8').toString('base64')}| >`
|
`<http://${key}ZZZ${Buffer.from(JSON.stringify(data), 'utf-8').toString('base64')}| >`
|
||||||
|
|
||||||
const decodeData = (key, message) => {
|
const decodeData = (key, message) => {
|
||||||
|
try {
|
||||||
const regex = new RegExp(`http://${key}ZZZ[^|]*`)
|
const regex = new RegExp(`http://${key}ZZZ[^|]*`)
|
||||||
let match = message.match(regex)
|
let match = message.match(regex)
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
@ -292,14 +301,31 @@ const decodeData = (key, message) => {
|
||||||
}
|
}
|
||||||
match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length
|
match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length
|
||||||
return JSON.parse(Buffer.from(match, 'base64').toString('utf-8'))
|
return JSON.parse(Buffer.from(match, 'base64').toString('utf-8'))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReaction = listener => reactionListeners.push(listener)
|
const onReaction = listener => reactionListeners.push(listener)
|
||||||
|
|
||||||
const channelIsIm = async channel => (await app.client.conversations.info({ channel }))?.channel?.is_im
|
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 }) => {
|
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 {
|
try {
|
||||||
await app.client.chat.delete({ channel: event.item.channel, ts: event.item.ts })
|
await app.client.chat.delete({ channel: event.item.channel, ts: event.item.ts })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -307,9 +333,12 @@ onReaction(async ({ event }) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setSlackAppClientChatUpdate(app.client.chat.update)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app,
|
app,
|
||||||
temperatureChannelId,
|
temperatureChannelId,
|
||||||
|
dailyStandupChannelId,
|
||||||
onAction: app.action,
|
onAction: app.action,
|
||||||
getMessage,
|
getMessage,
|
||||||
updateMessage: app.client.chat.update,
|
updateMessage: app.client.chat.update,
|
||||||
|
@ -319,10 +348,12 @@ module.exports = {
|
||||||
onReaction,
|
onReaction,
|
||||||
encodeData,
|
encodeData,
|
||||||
decodeData,
|
decodeData,
|
||||||
messageSage,
|
messageAdmin,
|
||||||
messageIn,
|
messageIn,
|
||||||
testMode,
|
testMode,
|
||||||
testId,
|
testId,
|
||||||
users,
|
users,
|
||||||
buildSayPrepend
|
buildSayPrepend,
|
||||||
|
pollTriggers,
|
||||||
|
pendingRestart
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue