New additions
Temp-change votes cancel each other out. More achievements. Better !buy layout. 2 new buyableItems. More advanced command flags. Centralize user and coin fetching for commands. Display hvacker's owned soul count. Rank leaderboard by prestige, then CPS. Start centralize some settings. Add: - Quack Store - Lore - Horror mode. - hidden payloads to messages. - Lightning strikes. - Cups game. - Emojis in non-dm !a calls. - Human-readable numbers. - Gamble-loss mockery. - Chaos. - Prestige emojis.
This commit is contained in:
parent
8577f6f272
commit
4e15721803
|
@ -6,8 +6,9 @@ const url = ''
|
||||||
const headers = new Headers()
|
const headers = new Headers()
|
||||||
headers.append('Authorization', 'Basic ' + base64.encode(config.honeywellKey + ':' + config.honeywellSecret))
|
headers.append('Authorization', 'Basic ' + base64.encode(config.honeywellKey + ':' + config.honeywellSecret))
|
||||||
|
|
||||||
fetch(url, {method:'GET',
|
fetch(url, {
|
||||||
headers: headers,
|
method: 'GET',
|
||||||
//credentials: 'user:passwd'
|
headers: headers
|
||||||
|
// credentials: 'user:passwd'
|
||||||
}).then(response => response.json())
|
}).then(response => response.json())
|
||||||
.then(json => console.log(json));
|
.then(json => console.log(json))
|
||||||
|
|
|
@ -5,17 +5,17 @@ const fs = require('fs')
|
||||||
const fileName = 'hvackerconfig.json'
|
const fileName = 'hvackerconfig.json'
|
||||||
|
|
||||||
const configPaths = [
|
const configPaths = [
|
||||||
path.join('/secrets', fileName),
|
path.join('/secrets', fileName),
|
||||||
path.join(homedir, '.add123', fileName)
|
path.join(homedir, '.add123', fileName)
|
||||||
]
|
]
|
||||||
|
|
||||||
const getConfigData = () => {
|
const getConfigData = () => {
|
||||||
const configPath = configPaths.find(fs.existsSync)
|
const configPath = configPaths.find(fs.existsSync)
|
||||||
if (!configPath) {
|
if (!configPath) {
|
||||||
throw 'Could not find a config file!'
|
throw 'Could not find a config file!'
|
||||||
}
|
}
|
||||||
const config = fs.readFileSync(configPath, 'utf8')
|
const config = fs.readFileSync(configPath, 'utf8')
|
||||||
return JSON.parse(config)
|
return JSON.parse(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = getConfigData()
|
module.exports = getConfigData()
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
const routine = require('./routine')
|
const routine = require('./routine')
|
||||||
|
|
||||||
const emptyBoard = [
|
const emptyBoard = [
|
||||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',],
|
[' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
||||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',],
|
[' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
||||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',],
|
[' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
||||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',],
|
[' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
||||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',],
|
[' ', ' ', ' ', ' ', ' ', ' ', ' '],
|
||||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',]
|
[' ', ' ', ' ', ' ', ' ', ' ', ' ']
|
||||||
]
|
]
|
||||||
|
|
||||||
const textFromBoard = board => {
|
const textFromBoard = board => {
|
||||||
|
@ -30,7 +30,7 @@ const checkRows = board => {
|
||||||
|
|
||||||
const checkColumns = board => {
|
const checkColumns = board => {
|
||||||
const colToText = i =>
|
const colToText = i =>
|
||||||
board[0][i] +
|
board[0][i] +
|
||||||
board[1][i] +
|
board[1][i] +
|
||||||
board[2][i] +
|
board[2][i] +
|
||||||
board[3][i] +
|
board[3][i] +
|
||||||
|
@ -54,7 +54,7 @@ const checkDiagonals = board => {
|
||||||
board[row][col] === board[row + 1][col + 1] &&
|
board[row][col] === board[row + 1][col + 1] &&
|
||||||
board[row][col] === board[row + 2][col + 2] &&
|
board[row][col] === board[row + 2][col + 2] &&
|
||||||
board[row][col] === board[row + 3][col + 3]
|
board[row][col] === board[row + 3][col + 3]
|
||||||
){
|
) {
|
||||||
return board[row][col]
|
return board[row][col]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ const checkDiagonals = board => {
|
||||||
board[row][col] === board[row + 1][col - 1] &&
|
board[row][col] === board[row + 1][col - 1] &&
|
||||||
board[row][col] === board[row + 2][col - 2] &&
|
board[row][col] === board[row + 2][col - 2] &&
|
||||||
board[row][col] === board[row + 3][col - 3]
|
board[row][col] === board[row + 3][col - 3]
|
||||||
){
|
) {
|
||||||
return board[row][col]
|
return board[row][col]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ const placeAt = (i, board, char) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeMove = (emoji, board) =>
|
const makeMove = (emoji, board) =>
|
||||||
placeAt(numEmojis.indexOf(emoji), board, getTurn(board))
|
placeAt(numEmojis.indexOf(emoji), board, getTurn(board))
|
||||||
|
|
||||||
routine.build({
|
routine.build({
|
||||||
startTriggers: ['connect 4', 'c4'],
|
startTriggers: ['connect 4', 'c4'],
|
||||||
|
@ -120,4 +120,4 @@ routine.build({
|
||||||
textFromBoard,
|
textFromBoard,
|
||||||
checkWinner: board => checkRows(board) || checkColumns(board) || checkDiagonals(board) || checkFull(board),
|
checkWinner: board => checkRows(board) || checkColumns(board) || checkDiagonals(board) || checkFull(board),
|
||||||
makeMove
|
makeMove
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,12 +2,12 @@ module.exports = {
|
||||||
leaderBoardViewer: {
|
leaderBoardViewer: {
|
||||||
name: 'Leaderboard-Viewer',
|
name: 'Leaderboard-Viewer',
|
||||||
description: 'Thank you for viewing the leaderboard!',
|
description: 'Thank you for viewing the leaderboard!',
|
||||||
emoji: 'trophy',
|
emoji: 'trophy'
|
||||||
},
|
},
|
||||||
seeTheQuade: {
|
seeTheQuade: {
|
||||||
name: 'See the Quade',
|
name: 'See the Quade',
|
||||||
description: 'Quade has appeared in your buyables',
|
description: 'Quade has appeared in your buyables',
|
||||||
emoji: 'quade',
|
emoji: 'quade'
|
||||||
},
|
},
|
||||||
greenCoin: {
|
greenCoin: {
|
||||||
name: 'Lucky Green Coin',
|
name: 'Lucky Green Coin',
|
||||||
|
@ -46,7 +46,7 @@ module.exports = {
|
||||||
emoji: 'mouse2'
|
emoji: 'mouse2'
|
||||||
},
|
},
|
||||||
weAllNeedHelp: {
|
weAllNeedHelp: {
|
||||||
name: `View the '!coin' help`,
|
name: 'View the \'!coin\' help',
|
||||||
description: 'We all need a little help sometimes',
|
description: 'We all need a little help sometimes',
|
||||||
emoji: 'grey_question'
|
emoji: 'grey_question'
|
||||||
},
|
},
|
||||||
|
@ -71,9 +71,19 @@ module.exports = {
|
||||||
description: 'You absolutely know how to party.',
|
description: 'You absolutely know how to party.',
|
||||||
emoji: 'sunglasses'
|
emoji: 'sunglasses'
|
||||||
},
|
},
|
||||||
|
itsOverNineHundred: {
|
||||||
|
name: 'Play the HVAC game 1000 times',
|
||||||
|
description: 'It\'s over nine hundred and ninety-nine!',
|
||||||
|
emoji: 'chart_with_upwards_trend'
|
||||||
|
},
|
||||||
youDisgustMe: {
|
youDisgustMe: {
|
||||||
name: 'You disgust me',
|
name: 'You disgust me',
|
||||||
description: 'Like, wow.',
|
description: 'Like, wow.',
|
||||||
emoji: 'nauseated_face'
|
emoji: 'nauseated_face'
|
||||||
|
},
|
||||||
|
bookWorm: {
|
||||||
|
name: 'Take a peek at the lore',
|
||||||
|
description: 'It\'t gotta be worth your time somehow.',
|
||||||
|
emoji: 'books'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const buyableItems = require('./buyableItems');
|
const buyableItems = require('./buyableItems')
|
||||||
const { commas, saveGame, setHighestCoins, addAchievement, getCoins, getUser, singleItemCps } = require('./utils');
|
const { commas, saveGame, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter } = require('./utils')
|
||||||
const slack = require('../../slack')
|
const slack = require('../../slack')
|
||||||
|
|
||||||
const calculateCost = ({ itemName, user, quantity = 1 }) => {
|
const calculateCost = ({ itemName, user, quantity = 1 }) => {
|
||||||
|
@ -29,37 +29,71 @@ const buildBlock = ({ user, itemName, cost, cps }) => ({
|
||||||
type: 'section',
|
type: 'section',
|
||||||
text: {
|
text: {
|
||||||
type: 'mrkdwn',
|
type: 'mrkdwn',
|
||||||
text: `${itemName} :${buyableItems[itemName].emoji}:x${user.items[itemName] || 0} - 𝕳${commas(cost)} - ${commas(cps)} CPS\n_${buyableItems[itemName].description}_`
|
text: `${itemName} :${buyableItems[itemName].emoji}:x${user.items[itemName] || 0} - H${commas(cost)} - ${commas(cps)} CPS\n_${buyableItems[itemName].description}_`
|
||||||
},
|
},
|
||||||
accessory: {
|
accessory: {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
text: {
|
text: {
|
||||||
type: 'plain_text',
|
type: 'plain_text',
|
||||||
text: 'Buy 1',
|
text: '1',
|
||||||
emoji: true
|
emoji: true
|
||||||
},
|
},
|
||||||
value: 'buy_' + itemName,
|
value: 'buy_' + itemName,
|
||||||
action_id: 'buy_' + itemName
|
action_id: 'buy_' + itemName
|
||||||
},
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildBlock2 = ({ user, itemName, cost, cps }) => ({
|
||||||
|
type: 'actions',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
text: {
|
||||||
|
type: 'plain_text',
|
||||||
|
text: 'Buy 1',
|
||||||
|
emoji: true
|
||||||
|
},
|
||||||
|
value: 'buy_' + itemName,
|
||||||
|
action_id: 'buy_' + itemName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
text: {
|
||||||
|
type: 'plain_text',
|
||||||
|
text: 'Buy 1',
|
||||||
|
emoji: true
|
||||||
|
},
|
||||||
|
value: 'buy_' + itemName,
|
||||||
|
action_id: 'buy_' + itemName
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const buyText2 = (highestCoins, user) => {
|
const buyText2 = (highestCoins, user) => {
|
||||||
return ({
|
return ({
|
||||||
text: buyableText(highestCoins, user),
|
text: buyableText(highestCoins, user),
|
||||||
blocks: Object.entries(buyableItems)
|
blocks: Object.entries(buyableItems)
|
||||||
.filter(canView(highestCoins))
|
.filter(canView(highestCoins))
|
||||||
.map(([itemName, item]) => {
|
.map(([itemName, item]) => {
|
||||||
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const buyRoute = async ({ event, say, words }) => {
|
const maxQuantity = ({ itemName, user, currentCoins }) => {
|
||||||
const user = getUser(event.user)
|
let quantity = 1
|
||||||
|
while (calculateCost({ itemName, user, quantity: quantity + 1 }) <= currentCoins) {
|
||||||
|
quantity++
|
||||||
|
}
|
||||||
|
return quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
const buyRoute = async ({ event, say, words, user }) => {
|
||||||
const buying = words[1]
|
const buying = words[1]
|
||||||
setHighestCoins(event.user)
|
setHighestCoins(event.user)
|
||||||
|
|
||||||
if (!buying) {
|
if (!buying) {
|
||||||
const highestCoins = user.highestEver || user.coins || 1
|
const highestCoins = user.highestEver || user.coins || 1
|
||||||
if (buyableItems.quade.baseCost < highestCoins * 100) {
|
if (buyableItems.quade.baseCost < highestCoins * 100) {
|
||||||
|
@ -75,14 +109,13 @@ const buyRoute = async ({ event, say, words }) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let quantity = 1
|
let quantity
|
||||||
const currentCoins = getCoins(event.user)
|
const currentCoins = user.coins
|
||||||
|
const max = maxQuantity({ itemName: buying, user, currentCoins })
|
||||||
if (words[2] === 'max') {
|
if (words[2] === 'max') {
|
||||||
while (calculateCost({ itemName: buying, user, quantity: quantity + 1 }) <= currentCoins) {
|
quantity = max
|
||||||
quantity++
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
quantity = parseInt(words[2] || '1')
|
quantity = Math.round(chaosFilter(parseInt(words[2] || '1'), 0.2, user, max) || 1)
|
||||||
}
|
}
|
||||||
if (!quantity || quantity < 1) {
|
if (!quantity || quantity < 1) {
|
||||||
await say('Quantity must be a positive integer')
|
await say('Quantity must be a positive integer')
|
||||||
|
@ -97,15 +130,17 @@ const buyRoute = async ({ event, say, words }) => {
|
||||||
user.coins -= realCost
|
user.coins -= realCost
|
||||||
user.items[buying] = user.items[buying] || 0
|
user.items[buying] = user.items[buying] || 0
|
||||||
user.items[buying] += quantity
|
user.items[buying] += quantity
|
||||||
console.log(buying, user.items.mouse)
|
|
||||||
if (buying === 'mouse' && user.items.mouse >= 100) {
|
if (buying === 'mouse' && user.items.mouse >= 100) {
|
||||||
addAchievement(user, 'ratGod', say)
|
addAchievement(user, 'ratGod', say)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quantity === 1) {
|
if (quantity === 1) {
|
||||||
await say(`You bought one :${buyable.emoji}:`)
|
await say(`You bought one :${buyable.emoji}:`)
|
||||||
} else {
|
} else {
|
||||||
await say(`You bought ${quantity} :${buyable.emoji}:`)
|
await say(`You bought ${quantity} :${buyable.emoji}:`)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveGame()
|
saveGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,11 +149,11 @@ const buyButton = async ({ body, ack, say, payload }) => {
|
||||||
const buying = payload.action_id.substring(4)
|
const buying = payload.action_id.substring(4)
|
||||||
console.log(`buyButton ${buying} clicked`)
|
console.log(`buyButton ${buying} clicked`)
|
||||||
const event = {
|
const event = {
|
||||||
user: body.user.id,
|
user: body.user.id
|
||||||
}
|
}
|
||||||
const user = getUser(event.user)
|
const user = getUser(event.user)
|
||||||
const words = ['', buying, '1']
|
const words = ['', buying, body.actions[0].text]
|
||||||
await buyRoute({ event, say, words })
|
await buyRoute({ event, say, words, 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,
|
||||||
|
@ -129,4 +164,4 @@ const buyButton = async ({ body, ack, say, payload }) => {
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -71,4 +71,16 @@ module.exports = {
|
||||||
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.'
|
||||||
},
|
},
|
||||||
|
smallBusiness: {
|
||||||
|
baseCost: 2_210_000_000_000_000,
|
||||||
|
earning: 2_845_000_000,
|
||||||
|
emoji: 'convenience_store',
|
||||||
|
description: 'The place where the creator of Hvacker goes to work.'
|
||||||
|
},
|
||||||
|
bigBusiness: {
|
||||||
|
baseCost: 26_210_000_000_000_000,
|
||||||
|
earning: 23_650_000_000,
|
||||||
|
emoji: 'office',
|
||||||
|
description: 'The place where the smallBusiness goes to work.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,113 @@
|
||||||
|
const { setHighestCoins, addAchievement, chaosFilter, commas, saveGame, getUser } = require('./utils')
|
||||||
|
const slack = require('../../slack')
|
||||||
|
|
||||||
|
let loreCount = 0
|
||||||
|
const l = (text, {correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => {
|
||||||
|
loreCount += 1
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
correctReactions,
|
||||||
|
correctResponse,
|
||||||
|
incorrectResponse,
|
||||||
|
jumpTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lore = [
|
||||||
|
l(`Allow me to tell you a story.`),
|
||||||
|
l(`Once upon a time, there were two young ducks named Genevieve and Isaiah.`),
|
||||||
|
l(`Isaiah was known mostly for being a big butthole whomst no one liked.`),
|
||||||
|
l(`Genevieve was known for a singing voice that could peel the paint off your car.`),
|
||||||
|
l(`Nevertheless, there's was a passionate love affair.`),
|
||||||
|
l(`_Honking_`),
|
||||||
|
l(`...`),
|
||||||
|
l(`Hey you know, it's rather cold, don't you think? Could you start a fire for us?`, {
|
||||||
|
correctReactions: ['fire'],
|
||||||
|
correctResponse: `Thank you.`,
|
||||||
|
incorrectResponse: `Well, you're looking in the right place, anyway.`,
|
||||||
|
}),
|
||||||
|
|
||||||
|
l(`Anyway, together they laid nine eggs.`),
|
||||||
|
l(`The first to hatch was Rick, who grew up into a fine bird with beautiful feathers.`),
|
||||||
|
l(`The second was Claire, who grew into an even finer bird, glowing with Duckish beauty.`),
|
||||||
|
l(`The third was Marf, who developed a severe addiction to Elmer's glue.`),
|
||||||
|
l(`Bird four was Yoink, who considered Tupperware a hobby.`),
|
||||||
|
l(`The fifth was Big Bob.`),
|
||||||
|
l(`The sixth was Jess, who became the number one checkers player in the world.`),
|
||||||
|
l(`The seventh was Small Bob.`),
|
||||||
|
l(`The eighth was Maurice, who had quite a few words to say about the French, and was eventually elected president.`),
|
||||||
|
l(`And the ninth...`),
|
||||||
|
l(`Well, the ninth might actually amount to something.`),
|
||||||
|
l(`https://i.imgur.com/eFreg7Y.gif\n`),
|
||||||
|
]
|
||||||
|
|
||||||
|
slack.onReaction(async ({ event, say }) => {
|
||||||
|
try {
|
||||||
|
const user = getUser(event.user)
|
||||||
|
const item = await slack.getMessage({ channel: event.item.channel, ts: event.item.ts })
|
||||||
|
const message = item.messages[0].text
|
||||||
|
const loreData = slack.decodeData('lore', message)
|
||||||
|
if (!loreData || user.lore !== loreData.index || !loreData.correctReactions) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!loreData.correctReactions.includes(event.reaction)) {
|
||||||
|
if (lore[user.lore].incorrectResponse) {
|
||||||
|
await say(lore[user.lore].incorrectResponse + encodeLore(user.lore))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(lore[user.lore])
|
||||||
|
await say(lore[user.lore].correctResponse)
|
||||||
|
user.lore += 1
|
||||||
|
saveGame()
|
||||||
|
} catch (e) {console.error(e)}
|
||||||
|
})
|
||||||
|
|
||||||
|
const encodeLore = loreNumber => lore[loreNumber].text.startsWith(':') && lore[loreNumber].text.endsWith(':') ? '' :
|
||||||
|
slack.encodeData('lore', {
|
||||||
|
index: loreNumber,
|
||||||
|
correctReactions: lore[loreNumber].correctReactions
|
||||||
|
})
|
||||||
|
|
||||||
|
const loreMessage = (user, say) => {
|
||||||
|
if (lore[user.lore]) {
|
||||||
|
return lore[user.lore].text + encodeLore(user.lore)
|
||||||
|
}
|
||||||
|
addAchievement(user, 'bookWorm', say)
|
||||||
|
return `Sorry. I'd love to tell you more, but I'm tired. Please check back later.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const loreRoute = async ({ say, words, user, isAdmin }) => {
|
||||||
|
user.lore ??= 0
|
||||||
|
if (!words[1]) {
|
||||||
|
const message = loreMessage(user, say)
|
||||||
|
await say(message)
|
||||||
|
if (!lore[user.lore]?.correctReactions) {
|
||||||
|
user.lore += 1
|
||||||
|
}
|
||||||
|
saveGame()
|
||||||
|
console.log('Sent ' + user.name + ':\n' + message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (words[1] === 'reset') {
|
||||||
|
user.lore = 0
|
||||||
|
saveGame()
|
||||||
|
return say(`I have reset your place in the story.`)
|
||||||
|
}
|
||||||
|
if (isAdmin) {
|
||||||
|
if (words[1] === 'all') {
|
||||||
|
let loreMessage = ''
|
||||||
|
for (let i = 0; i < user.lore; i++) {
|
||||||
|
loreMessage += lore[i].text + (lore[i].correctResponse || '') + '\n'
|
||||||
|
}
|
||||||
|
return say(loreMessage)
|
||||||
|
}
|
||||||
|
const jumpTo = parseInt(words[1])
|
||||||
|
if (!isNaN(jumpTo)) {
|
||||||
|
user.lore = jumpTo
|
||||||
|
saveGame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = loreRoute
|
|
@ -1,111 +1,107 @@
|
||||||
const { getUser, getCoins, commas, saveGame } = require('./utils');
|
const { commas, saveGame, quackGradeMultiplier, prestigeMultiplier, makeBackup } = require('./utils')
|
||||||
const quackStore = require('./quackstore');
|
const { quackStore } = require('./quackstore')
|
||||||
|
|
||||||
const possiblePrestige = coins => {
|
const possiblePrestige = coins => {
|
||||||
let p = 0
|
let p = 0
|
||||||
while (tpcRec(p + 1) <= coins) {
|
while (totalCostForPrestige(p + 1) <= coins) {
|
||||||
p += 1
|
p += 1
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalCostForPrestige = prestigeLevel => {
|
|
||||||
let cost = 0
|
|
||||||
while (prestigeLevel) {
|
|
||||||
cost += 1_000_000_000_000 * Math.pow(prestigeLevel, 3)
|
|
||||||
prestigeLevel -= 1
|
|
||||||
}
|
|
||||||
return cost
|
|
||||||
}
|
|
||||||
|
|
||||||
const tpcRecMemo = []
|
const tpcRecMemo = []
|
||||||
const tpcRec = prestigeLevel => {
|
const totalCostForPrestige = prestigeLevel => {
|
||||||
if (prestigeLevel === 0) {
|
if (prestigeLevel === 0) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return (tpcRecMemo[prestigeLevel]) || (tpcRecMemo[prestigeLevel] = 1_000_000_000_000 * Math.pow(prestigeLevel, 3) + tpcRec(prestigeLevel - 1))
|
return (tpcRecMemo[prestigeLevel]) || (tpcRecMemo[prestigeLevel] = 1_000_000_000_000 * Math.pow(prestigeLevel, 3) + totalCostForPrestige(prestigeLevel - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
const prestigeRoute = async ({ say, words, user }) => {
|
||||||
const prestigeRoute = async ({ event, say, words }) => {
|
const possible = possiblePrestige(user.coinsAllTime)
|
||||||
const user = getUser(event.user)
|
const current = user.prestige ??= 0
|
||||||
getCoins(event.user)
|
if (words[1] === 'me') {
|
||||||
const possible = possiblePrestige(user.coinsAllTime)
|
await say(
|
||||||
const current = user.prestige ??= 0
|
'This will permanently remove all of your items, upgrades, and coins!\n\n' +
|
||||||
if (words[1] === 'me') {
|
|
||||||
await say(
|
|
||||||
'This will permanently remove all of your items, upgrades, and coins!\n\n' +
|
|
||||||
'Say \'!!prestige me\' to confirm.'
|
'Say \'!!prestige me\' to confirm.'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
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(Math.round(tpcRec(possible + 1) - user.coinsAllTime))}\n\n` +
|
`HVAC until next quack: ${commas(totalCostForPrestige(possible + 1) - user.coinsAllTime)}\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)}%`
|
||||||
}
|
)
|
||||||
}//, true, adminOnly)
|
}
|
||||||
|
}//, true, adminOnly)
|
||||||
|
|
||||||
// TODO
|
const prestigeConfirmRoute = async ({ event, say, user }) => {
|
||||||
const prestigeConfirmRoute = async ({ event, say, words }) => {
|
const possible = possiblePrestige(user.coinsAllTime)
|
||||||
const user = getUser(event.user)
|
const current = user.prestige
|
||||||
getCoins(event.user)
|
if (possible <= current) {
|
||||||
const possible = possiblePrestige(user.coinsAllTime)
|
await say('You don\'t have enough HVAC to prestige right now!')
|
||||||
const current = user.prestige
|
return
|
||||||
if (possible <= current) {
|
}
|
||||||
await say('You don\'t have enough HVAC to prestige right now!')
|
if (event?.text !== '!!prestige me') {
|
||||||
return
|
await say('Say exactly \'!!prestige me\' to confirm')
|
||||||
}
|
return
|
||||||
if (event?.text !== '!!prestige me') {
|
}
|
||||||
await say('Say exactly \'!!prestige me\' to confirm')
|
await makeBackup()
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log('possible', possible)
|
|
||||||
console.log('user.prestige', user.prestige)
|
|
||||||
user.quacks ??= 0
|
|
||||||
user.quacks += (possible - user.prestige)
|
|
||||||
console.log('user.quacks', user.quacks)
|
|
||||||
user.prestige = possible
|
|
||||||
user.coins = 0
|
|
||||||
user.items = {}
|
|
||||||
user.upgrades = {}
|
|
||||||
saveGame()
|
|
||||||
await say('You prestiged! Check out !quackstore to see what you can buy!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const quackStoreListing = ([name, upgrade]) =>
|
user.quacks ??= 0
|
||||||
`:${upgrade.emoji}: *${name}* - Costs *${upgrade.cost} Quack.*\n\n_${upgrade.description}_`
|
user.quacks += (possible - user.prestige)
|
||||||
|
|
||||||
|
user.prestige = possible
|
||||||
|
user.coins = 0
|
||||||
|
user.items = {}
|
||||||
|
user.upgrades = {}
|
||||||
|
|
||||||
|
saveGame()
|
||||||
|
await say('You prestiged! Check out !quackstore to see what you can buy!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const quackStoreListing = (showCost = true) => ([name, upgrade]) =>
|
||||||
|
showCost
|
||||||
|
? `:${upgrade.emoji}: *${name}* - Costs *${upgrade.cost} Quack.*\n\n_${upgrade.description}_`
|
||||||
|
: `:${upgrade.emoji}: *${name}* - Worth *${upgrade.cost} Quack.*\n\n_${upgrade.description}_`
|
||||||
|
|
||||||
const allUserQuackUpgrades = user =>
|
const allUserQuackUpgrades = user =>
|
||||||
Object.entries(user.quackUpgrades || {})
|
Object.entries(user.quackUpgrades || {})
|
||||||
.map(([type, upgrades]) => upgrades)
|
.map(([type, upgrades]) => upgrades).flatMap(x => x)
|
||||||
|
|
||||||
const hasPreReqs = user => ([name, upgrade]) => {
|
const hasPreReqs = user => ([name, upgrade]) => {
|
||||||
if (!upgrade.preReqs) {
|
if (!upgrade.preReqs) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const allUserUpgrades = allUserQuackUpgrades(user)
|
const allUserUpgrades = allUserQuackUpgrades(user)
|
||||||
console.log(allUserUpgrades)
|
|
||||||
return upgrade.preReqs.every(preReq => allUserUpgrades.includes(preReq))
|
return upgrade.preReqs.every(preReq => allUserUpgrades.includes(preReq))
|
||||||
}
|
}
|
||||||
|
|
||||||
const quackStoreText = user =>
|
const owns = (user, [name, upgrade]) => allUserQuackUpgrades(user).includes(name)
|
||||||
Object.entries(quackStore)
|
|
||||||
.filter(hasPreReqs(user))
|
|
||||||
.map(quackStoreListing)
|
|
||||||
.join('\n\n')
|
|
||||||
|
|
||||||
const quackStoreRoute = async ({ event, say, words }) => {
|
const ownedQuackItems = user => Object.entries(quackStore).filter(upgrade => owns(user, upgrade))
|
||||||
const user = getUser(event.user)
|
|
||||||
|
const unownedQuackItems = user => Object.entries(quackStore).filter(upgrade => !owns(user, upgrade))
|
||||||
|
|
||||||
|
const quackStoreText = user =>
|
||||||
|
unownedQuackItems(user)
|
||||||
|
.filter(hasPreReqs(user))
|
||||||
|
.map(quackStoreListing(true))
|
||||||
|
.join('\n\n') +
|
||||||
|
`\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
|
||||||
|
`\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%`
|
||||||
|
|
||||||
|
const quackStoreRoute = async ({ user, say, words }) => {
|
||||||
user.quackUpgrades ??= {}
|
user.quackUpgrades ??= {}
|
||||||
const quacks = user.quacks ??= 0
|
const quacks = user.quacks ??= 0
|
||||||
if (!words[1]) {
|
if (!words[1]) {
|
||||||
await say(quackStoreText(user))
|
await say(quackStoreText(user))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log(`Trying to buy ${words[1]}`)
|
||||||
const quackItem = quackStore[words[1]]
|
const quackItem = quackStore[words[1]]
|
||||||
if (!quackItem) {
|
if (!quackItem || !unownedQuackItems(user).find(([name]) => name === words[1])) {
|
||||||
await say(`'${words[1]}' is not available in the quack store!`)
|
await say(`'${words[1]}' is not available in the quack store!`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -113,13 +109,28 @@ const quackStoreRoute = async ({ event, say, words }) => {
|
||||||
await say(`${words[1]} costs ${quackItem.cost} Quacks, but you only have ${quacks}!`)
|
await say(`${words[1]} costs ${quackItem.cost} Quacks, but you only have ${quacks}!`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
user.quacks -= quackItem.cost
|
||||||
user.quackUpgrades[quackItem.type] ??= []
|
user.quackUpgrades[quackItem.type] ??= []
|
||||||
user.quackUpgrades[quackItem.type].push(words[1])
|
user.quackUpgrades[quackItem.type].push(words[1])
|
||||||
saveGame()
|
saveGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ownedQuacksText = user =>
|
||||||
|
ownedQuackItems(user)
|
||||||
|
.filter(hasPreReqs(user))
|
||||||
|
.map(quackStoreListing(false))
|
||||||
|
.join('\n\n') +
|
||||||
|
`\n\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%`
|
||||||
|
|
||||||
|
const ownedQuacksRoute = async ({ say, user }) => {
|
||||||
|
user.quackUpgrades ??= {}
|
||||||
|
user.quacks ??= 0
|
||||||
|
await say(ownedQuacksText(user))
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
quackStoreRoute,
|
quackStoreRoute,
|
||||||
prestigeRoute,
|
prestigeRoute,
|
||||||
prestigeConfirmRoute
|
prestigeConfirmRoute,
|
||||||
|
ownedQuacksRoute
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
const getRandomFromArray = array => array[Math.floor(Math.random() * array.length)]
|
||||||
|
|
||||||
|
const chaosCpsMods = [3, 2, 0.1, 1, 1.5, 1.6, 0, 1.1, 1.1, 1.26]
|
||||||
|
const chaosAvg = () => chaosCpsMods.reduce((total, next) => total + next, 0) / chaosCpsMods.length
|
||||||
|
//const getChaos = offset => chaosCpsMods[(Math.floor(new Date().getSeconds() / chaosCpsMods.length) + offset) % chaosCpsMods.length]
|
||||||
|
const getChaos = offset => chaosCpsMods[(Math.floor(new Date().getSeconds() / chaosCpsMods.length)) % chaosCpsMods.length]
|
||||||
|
|
||||||
const quackStore = {
|
const quackStore = {
|
||||||
ascent: {
|
ascent: {
|
||||||
name: 'Ascent',
|
name: 'Ascent',
|
||||||
|
@ -16,5 +23,24 @@ const quackStore = {
|
||||||
effect: cps => cps * 1.2,
|
effect: cps => cps * 1.2,
|
||||||
cost: 5
|
cost: 5
|
||||||
},
|
},
|
||||||
|
chaos: {
|
||||||
|
name: 'Chaos',
|
||||||
|
type: 'cps',
|
||||||
|
emoji: 'eye',
|
||||||
|
description: 'Awaken. Gives a random modifier to your CPS every six seconds. May have other consequences...',
|
||||||
|
//+ '_\n_Averages a 26% CPS boost.',
|
||||||
|
preReqs: ['nuclearFuel'],
|
||||||
|
effect: (cps, user) => {
|
||||||
|
if (user.name !== 'Sage') {
|
||||||
|
console.log('Chaos Multiplier', getChaos(Math.round(user.interactions / 50)))
|
||||||
|
}
|
||||||
|
return cps * getChaos(Math.round(user.interactions / 50))
|
||||||
|
},
|
||||||
|
cost: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
quackStore,
|
||||||
|
getChaos: user => getChaos(user.interactions || 0)
|
||||||
}
|
}
|
||||||
module.exports = quackStore
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
horrorEnabled: false
|
||||||
|
}
|
|
@ -11,191 +11,209 @@ module.exports = {
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fasterComputers: basic({
|
fasterComputers: basic({
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
biggerBlowhole: basic({
|
biggerBlowhole: basic({
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
greasyTracks: basic({
|
greasyTracks: basic({
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
gasolineFire: basic({
|
gasolineFire: basic({
|
||||||
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({
|
||||||
type: 'fire',
|
type: 'fire',
|
||||||
description: 'Use the ignite command for a secret achievement.',
|
description: 'Use the ignite command for a secret achievement.',
|
||||||
count: 10,
|
count: 10,
|
||||||
cost: 163_000_000,
|
cost: 163_000_000
|
||||||
}),
|
}),
|
||||||
cavemanFire: basic({
|
cavemanFire: basic({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
spoonerang: basic({
|
spoonerang: basic({
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lunarPower: basic({
|
lunarPower: basic({
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
glassButterfly: basic({
|
glassButterfly: basic({
|
||||||
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({
|
||||||
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({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
silverMirror: basic({
|
silverMirror: basic({
|
||||||
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({
|
||||||
|
type: 'mirror',
|
||||||
|
description: 'Take your self-reflection on the go!',
|
||||||
|
count: 10,
|
||||||
|
cost: 18_000_000_000_000
|
||||||
}),
|
}),
|
||||||
window: basic({
|
window: basic({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fzero: basic({
|
fzero: basic({
|
||||||
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({
|
||||||
|
type: 'quade',
|
||||||
|
description: 'YEE HAW :trimedary_camel:',
|
||||||
|
count: 10,
|
||||||
|
cost: 200_000_000_000_000
|
||||||
}),
|
}),
|
||||||
adam: basic({
|
adam: basic({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
latestNode: basic({
|
latestNode: basic({
|
||||||
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({
|
||||||
|
type: 'hvacker',
|
||||||
|
description: 'Sometimes javascript just isn\'t fast enough.',
|
||||||
|
count: 10,
|
||||||
|
cost: 3_300_000_000_000_000
|
||||||
}),
|
}),
|
||||||
gitCommits: basic({
|
gitCommits: basic({
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
|
||||||
homage: {
|
homage: {
|
||||||
|
@ -204,7 +222,7 @@ module.exports = {
|
||||||
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,
|
||||||
emoji: 'cookie',
|
emoji: 'cookie',
|
||||||
cost: 10_000_000_000,
|
cost: 10_000_000_000,
|
||||||
effect: (itemCps, user) => Math.ceil(itemCps * 1.1)
|
effect: (itemCps, user) => itemCps * 1.1
|
||||||
},
|
},
|
||||||
iLoveHvac: {
|
iLoveHvac: {
|
||||||
type: 'general',
|
type: 'general',
|
||||||
|
@ -212,7 +230,7 @@ module.exports = {
|
||||||
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) => Math.ceil(itemCps * 1.1)
|
effect: (itemCps, user) => itemCps * 1.1
|
||||||
}
|
}
|
||||||
|
|
||||||
// moreUpgrades: {
|
// moreUpgrades: {
|
||||||
|
@ -224,4 +242,3 @@ module.exports = {
|
||||||
// effect: nothing
|
// effect: nothing
|
||||||
// },
|
// },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,38 @@
|
||||||
const fs = require('fs')
|
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 upgrades = require('./upgrades')
|
||||||
const quackStore = require("./quackstore");
|
const { quackStore, getChaos } = require('./quackstore')
|
||||||
|
|
||||||
const saveFile = 'hvacoins.json'
|
const saveFile = 'hvacoins.json'
|
||||||
|
|
||||||
const logError = msg => msg ? console.error(msg) : () => { /* Don't log empty message */ }
|
const logError = msg => msg ? console.error(msg) : () => { /* Don't log empty message */ }
|
||||||
|
|
||||||
const loadGame = () => parseOr(fs.readFileSync('./' + saveFile, 'utf-8'),
|
const loadGame = () => {
|
||||||
() => ({
|
const game = parseOr(fs.readFileSync('./' + saveFile, 'utf-8'),
|
||||||
users: {},
|
() => ({
|
||||||
nfts: [],
|
users: {},
|
||||||
squad: {}
|
nfts: [],
|
||||||
}))
|
squad: {},
|
||||||
|
horrors: {}
|
||||||
|
}))
|
||||||
|
game.horrors ??= {}
|
||||||
|
return game
|
||||||
|
}
|
||||||
|
|
||||||
|
const chaosFilter = (num, odds, user, max = Infinity) => {
|
||||||
|
const userQuackgrades = user.quackUpgrades?.cps || []
|
||||||
|
const hasChaos = userQuackgrades.includes('chaos')
|
||||||
|
if (!hasChaos || Math.random() < odds) {
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
const chaosed = num * getChaos(user)
|
||||||
|
if (chaosed > max) {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return chaosed
|
||||||
|
}
|
||||||
|
|
||||||
const parseOr = (parseable, orFunc) => {
|
const parseOr = (parseable, orFunc) => {
|
||||||
try {
|
try {
|
||||||
|
@ -25,10 +43,16 @@ const parseOr = (parseable, orFunc) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeBackup = () => {
|
||||||
|
const fileName = './backups/' + saveFile + new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_')
|
||||||
|
console.log(`Making backup file: ${fileName}`)
|
||||||
|
fs.writeFileSync(fileName, JSON.stringify(game))
|
||||||
|
}
|
||||||
|
|
||||||
let saves = 0
|
let saves = 0
|
||||||
const saveGame = () => {
|
const saveGame = () => {
|
||||||
if (saves % 100 === 0) {
|
if (saves % 100 === 0) {
|
||||||
fs.writeFileSync('./backups/' + saveFile + new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_'), JSON.stringify(game))
|
makeBackup()
|
||||||
}
|
}
|
||||||
saves += 1
|
saves += 1
|
||||||
fs.writeFileSync('./' + saveFile, JSON.stringify(game, null, 2))
|
fs.writeFileSync('./' + saveFile, JSON.stringify(game, null, 2))
|
||||||
|
@ -54,7 +78,82 @@ const idFromWord = word => {
|
||||||
|
|
||||||
const getSeconds = () => new Date().getTime() / 1000
|
const getSeconds = () => new Date().getTime() / 1000
|
||||||
|
|
||||||
const commas = num => num.toLocaleString()
|
const bigNumberWords = [
|
||||||
|
['tredecillion', 1_000_000_000_000_000_000_000_000_000_000_000_000_000_000],
|
||||||
|
['duodecillion', 1_000_000_000_000_000_000_000_000_000_000_000_000_000],
|
||||||
|
['undecillion', 1_000_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],
|
||||||
|
['octillion', 1_000_000_000_000_000_000_000_000_000],
|
||||||
|
['septtillion', 1_000_000_000_000_000_000_000_000],
|
||||||
|
['sextillion', 1_000_000_000_000_000_000_000],
|
||||||
|
['quintillion', 1_000_000_000_000_000_000],
|
||||||
|
['quadrillion', 1_000_000_000_000_000],
|
||||||
|
['trillion', 1_000_000_000_000],
|
||||||
|
['billion', 1_000_000_000],
|
||||||
|
['million', 1_000_000],
|
||||||
|
]
|
||||||
|
|
||||||
|
const commas = (num, precise = false) => {
|
||||||
|
num = Math.round(num)
|
||||||
|
const bigNum = bigNumberWords.find(([, base]) => num >= base)
|
||||||
|
if (bigNum && !precise) {
|
||||||
|
const [name, base] = bigNum
|
||||||
|
const nummed = (num / base).toPrecision(3)
|
||||||
|
return `${nummed} ${name}`
|
||||||
|
}
|
||||||
|
return num.toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseAll = (str, allNum) => {
|
||||||
|
if (!str) {
|
||||||
|
return NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.toLowerCase()?.replace(/,/g, '')
|
||||||
|
|
||||||
|
if (str === 'all') {
|
||||||
|
return allNum
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (str) {
|
||||||
|
case 'my soul':
|
||||||
|
return allNum
|
||||||
|
case 'sex':
|
||||||
|
case 'sex number':
|
||||||
|
return 69_000_000
|
||||||
|
case ':maple_leaf:':
|
||||||
|
case ':herb:':
|
||||||
|
case 'weed':
|
||||||
|
case 'weed number':
|
||||||
|
return 420_000_000
|
||||||
|
case 'a milli':
|
||||||
|
return 1_000_000
|
||||||
|
case 'a band':
|
||||||
|
return 1000
|
||||||
|
case ':100:':
|
||||||
|
case 'one hunna':
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('STR', str)
|
||||||
|
if (str.match(/^\d+$/)) {
|
||||||
|
console.log('parseInt()')
|
||||||
|
return parseInt(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str.match(/^\d+\.\d+$/)) {
|
||||||
|
console.log('parseFloat()')
|
||||||
|
return Math.round(parseFloat(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
const bigNum = bigNumberWords.find(([name]) => str.endsWith(name))
|
||||||
|
if (bigNum && str.match(/^\d+(\.\d+)?/)) {
|
||||||
|
return Math.round(parseFloat(str) * bigNum[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return NaN
|
||||||
|
}
|
||||||
|
|
||||||
const game = loadGame()
|
const game = loadGame()
|
||||||
const { users, nfts, squad } = game
|
const { users, nfts, squad } = game
|
||||||
|
@ -107,7 +206,7 @@ const getCoins = userId => {
|
||||||
const lastCheck = user.lastCheck || currentTime
|
const lastCheck = user.lastCheck || currentTime
|
||||||
const secondsPassed = currentTime - lastCheck
|
const secondsPassed = currentTime - lastCheck
|
||||||
|
|
||||||
const increase = getCPS(userId) * secondsPassed
|
const increase = getCPS(user) * secondsPassed
|
||||||
user.coins += increase
|
user.coins += increase
|
||||||
user.coinsAllTime += increase
|
user.coinsAllTime += increase
|
||||||
user.coins = Math.floor(user.coins)
|
user.coins = Math.floor(user.coins)
|
||||||
|
@ -118,8 +217,7 @@ const getCoins = userId => {
|
||||||
return user.coins
|
return user.coins
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCPS = userId => {
|
const getCPS = user => {
|
||||||
const user = getUser(userId)
|
|
||||||
const userItems = user?.items || {}
|
const userItems = user?.items || {}
|
||||||
return Math.round(Object.keys(userItems).reduce((total, itemName) => total + getItemCps(user, itemName), 0))
|
return Math.round(Object.keys(userItems).reduce((total, itemName) => total + getItemCps(user, itemName), 0))
|
||||||
}
|
}
|
||||||
|
@ -130,14 +228,14 @@ 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 => Math.ceil(cps * 1.2),
|
effect: cps => cps * 1.2,
|
||||||
cost: 10_000_000_000_000,
|
cost: 10_000_000_000_000,
|
||||||
emoji: 'keyboard'
|
emoji: 'keyboard'
|
||||||
},
|
},
|
||||||
copyPasteMacro: {
|
copyPasteMacro: {
|
||||||
name: 'Copy-Paste Macro.',
|
name: 'Copy-Paste Macro.',
|
||||||
description: 'Don\'t actually use this. Boosts CPS by 20% for everyone.',
|
description: 'Don\'t actually use this. Boosts CPS by 20% for everyone.',
|
||||||
effect: cps => Math.ceil(cps * 1.2),
|
effect: cps => cps * 1.2,
|
||||||
cost: 100_000_000_000_000,
|
cost: 100_000_000_000_000,
|
||||||
emoji: 'printer'
|
emoji: 'printer'
|
||||||
}
|
}
|
||||||
|
@ -147,35 +245,122 @@ const squadHas = ([name]) => squad.upgrades[name] === true
|
||||||
const squadIsMissing = name => !squadHas(name)
|
const squadIsMissing = name => !squadHas(name)
|
||||||
|
|
||||||
const getCompletedSquadgrades = () =>
|
const getCompletedSquadgrades = () =>
|
||||||
Object.entries(squadUpgrades)
|
Object.entries(squadUpgrades)
|
||||||
.filter(squadHas)
|
.filter(squadHas)
|
||||||
.map(([, upgrade]) => upgrade)
|
.map(([, upgrade]) => upgrade)
|
||||||
|
|
||||||
|
const prestigeMultiplier = user => 1 + ((user.prestige || 0) * 0.01)
|
||||||
|
|
||||||
|
const quackGradeMultiplier = user => {
|
||||||
|
const userQuackgrades = user.quackUpgrades?.cps || []
|
||||||
|
return userQuackgrades.reduce((total, upgrade) => quackStore[upgrade].effect(total, user), 1)
|
||||||
|
}
|
||||||
|
|
||||||
const singleItemCps = (user, itemName) => {
|
const singleItemCps = (user, itemName) => {
|
||||||
const baseCps = buyableItems[itemName].earning
|
const baseCps = buyableItems[itemName].earning
|
||||||
|
// console.log('')
|
||||||
|
// console.log(`${itemName} CPS:`)
|
||||||
|
// console.log('baseCps', baseCps)
|
||||||
|
|
||||||
const itemUpgrades = (user.upgrades[itemName] || []).map(name => upgrades[name])
|
const itemUpgrades = (user.upgrades[itemName] || []).map(name => upgrades[name])
|
||||||
const itemUpgradeCps = itemUpgrades.reduce((totalCps, upgrade) => upgrade.effect(totalCps, user), baseCps)
|
const itemUpgradeCps = itemUpgrades.reduce((totalCps, upgrade) => upgrade.effect(totalCps, user), 1)
|
||||||
|
// console.log('itemUpgradeCps', itemUpgradeCps)
|
||||||
|
|
||||||
const userGeneralUpgrades = user.upgrades.general || []
|
const userGeneralUpgrades = user.upgrades.general || []
|
||||||
const generalUpgradeCps = Object.entries(userGeneralUpgrades).reduce((total, [, upgradeName]) => upgrades[upgradeName].effect(total, user), itemUpgradeCps)
|
const generalUpgradeCps = Object.entries(userGeneralUpgrades).reduce((total, [, upgradeName]) => upgrades[upgradeName].effect(total, user), 1)
|
||||||
|
// console.log('generalUpgradeCps', generalUpgradeCps)
|
||||||
|
|
||||||
const achievementCount = Object.keys(user.achievements || {}).length
|
const achievementCount = Object.keys(user.achievements || {}).length
|
||||||
const achievementMultiplier = Math.pow(1.01, achievementCount)
|
const achievementMultiplier = Math.pow(1.01, achievementCount)
|
||||||
|
// console.log('achievementMultiplier', achievementMultiplier)
|
||||||
|
|
||||||
const userQuackgrades = user.quackUpgrades?.cps || []
|
const quackGrade = quackGradeMultiplier(user)
|
||||||
const quackMultiplier = userQuackgrades.reduce((total, upgrade) => quackStore[upgrade].effect(total, user), 1)
|
// console.log('quackgrade', quackGrade)
|
||||||
|
|
||||||
const prestigeMultiplier = 1 + ((user.prestige || 0) * 0.01)
|
const pMult = prestigeMultiplier(user)
|
||||||
|
// console.log('prestigeMultiplier', pMult)
|
||||||
|
|
||||||
return achievementMultiplier *
|
const squadGradeMultiplier = getCompletedSquadgrades().reduce((cps, upgrade) => upgrade.effect(cps), 1)
|
||||||
quackMultiplier *
|
// console.log('squadGradeMultiplier', squadGradeMultiplier)
|
||||||
prestigeMultiplier *
|
|
||||||
getCompletedSquadgrades().reduce((cps, upgrade) => upgrade.effect(cps), generalUpgradeCps)
|
const total =
|
||||||
|
baseCps *
|
||||||
|
achievementMultiplier *
|
||||||
|
itemUpgradeCps *
|
||||||
|
generalUpgradeCps *
|
||||||
|
quackGrade *
|
||||||
|
pMult *
|
||||||
|
squadGradeMultiplier
|
||||||
|
|
||||||
|
// console.log('Single Item CPS:', total)
|
||||||
|
|
||||||
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shuffle = str => str.split('').sort(() => 0.5 - Math.random()).join('')
|
||||||
|
|
||||||
|
const shufflePercent = (str, percentOdds) => {
|
||||||
|
const shuffled = shuffle(str)
|
||||||
|
let partiallyShuffled = ''
|
||||||
|
const shuffleChar = () => Math.random() < percentOdds
|
||||||
|
|
||||||
|
let isEmoji = false
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
if (str[i] === ':') {
|
||||||
|
isEmoji = !isEmoji
|
||||||
|
}
|
||||||
|
if (isEmoji) { // Less likely to shuffle emojis
|
||||||
|
partiallyShuffled += (shuffleChar() && shuffleChar()) ? shuffled[i] : str[i]
|
||||||
|
} else {
|
||||||
|
partiallyShuffled += shuffleChar() ? shuffled[i] : str[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return partiallyShuffled
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitelyShuffle = (str, percentOdds) => {
|
||||||
|
if (!str) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
if (!percentOdds) {
|
||||||
|
percentOdds = 0.01
|
||||||
|
}
|
||||||
|
let shuffled = str
|
||||||
|
while (shuffled === str) {
|
||||||
|
shuffled = shufflePercent(str, percentOdds)
|
||||||
|
}
|
||||||
|
return shuffled
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRandomFromArray = array => array[Math.floor(Math.random() * array.length)]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds reactions to the given message, in order.
|
||||||
|
* If adding any reaction is a failure, it will continue on to the next.
|
||||||
|
*
|
||||||
|
* @param app The slack bolt app
|
||||||
|
* @param channelId The id of the channel the message is in
|
||||||
|
* @param timestamp The timestamp of the message
|
||||||
|
* @param reactions An array of reactions to add
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const addReactions = async ({ app, channelId, timestamp, reactions }) => {
|
||||||
|
for (const reaction of reactions) {
|
||||||
|
try {
|
||||||
|
await app.client.reactions.add({
|
||||||
|
channel: channelId,
|
||||||
|
timestamp,
|
||||||
|
name: reaction
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
module.exports = {
|
module.exports = {
|
||||||
saveGame,
|
saveGame,
|
||||||
|
makeBackup,
|
||||||
logError,
|
logError,
|
||||||
parseOr,
|
parseOr,
|
||||||
maybeNews,
|
maybeNews,
|
||||||
|
@ -190,5 +375,13 @@ module.exports = {
|
||||||
getItemCps,
|
getItemCps,
|
||||||
squadUpgrades,
|
squadUpgrades,
|
||||||
squadIsMissing,
|
squadIsMissing,
|
||||||
|
prestigeMultiplier,
|
||||||
|
quackGradeMultiplier,
|
||||||
|
shufflePercent,
|
||||||
|
definitelyShuffle,
|
||||||
|
parseAll,
|
||||||
|
getRandomFromArray,
|
||||||
|
chaosFilter,
|
||||||
|
addReactions,
|
||||||
game
|
game
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,10 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
|
||||||
console.log(' rate limited')
|
console.log(' rate limited')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log(` went through for ${slack.ourUsers[event.user]}`)
|
console.log(` went through for ${slack.users[event.user]}`)
|
||||||
userGetter.users[event.user].lastApiCall = currentTime
|
userGetter.users[event.user].lastApiCall = currentTime
|
||||||
|
|
||||||
await action({event, say, words})
|
await action({ event, say, words })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
await say(e.stack)
|
await say(e.stack)
|
||||||
|
@ -66,4 +66,4 @@ module.exports = {
|
||||||
launch: () => app.listen(port, () => {
|
launch: () => app.listen(port, () => {
|
||||||
console.log(`Express listening on port ${port}`)
|
console.log(`Express listening on port ${port}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,9 @@ const buildGameStarter = ({ startTriggers, dataName, gameName, textFromBoard, in
|
||||||
if (event.channel_type === 'im') {
|
if (event.channel_type === 'im') {
|
||||||
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))) {
|
||||||
|
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)])
|
||||||
const msg = messageFromBoard({
|
const msg = messageFromBoard({
|
||||||
dataName,
|
dataName,
|
||||||
gameName,
|
gameName,
|
||||||
|
@ -50,7 +52,8 @@ const getMessages = winner => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turnChoiceEmojis, makeMove }) => async ({ event, say }) => {
|
const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turnChoiceEmojis, makeMove }) => async ({ event, say }) => {
|
||||||
if (event.item_user !== slack.hvackerBotUserId || !turnChoiceEmojis.includes(event.reaction)) {
|
if (event.item_user !== slack.users.Hvacker || !turnChoiceEmojis.includes(event.reaction)) {
|
||||||
|
console.log('bad item_user/reaction')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,12 +64,14 @@ const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turn
|
||||||
|
|
||||||
const game = decodeGame(dataName, message.messages[0].text)
|
const game = decodeGame(dataName, message.messages[0].text)
|
||||||
if (!game) {
|
if (!game) {
|
||||||
|
console.log('could not decode game')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { board, players } = game
|
const { board, players } = game
|
||||||
let winner = checkWinner(board)
|
let winner = checkWinner(board)
|
||||||
if (winner) {
|
if (winner) {
|
||||||
|
console.log('winner found: ' + winner)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +106,7 @@ const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turn
|
||||||
name: emojiName
|
name: emojiName
|
||||||
})
|
})
|
||||||
turnChoiceEmojis.forEach(removeEmoji)
|
turnChoiceEmojis.forEach(removeEmoji)
|
||||||
|
console.log('SENDING to ' + 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
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
const routine = require("./routine");
|
const routine = require('./routine')
|
||||||
|
|
||||||
const emptyBoard = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
|
const emptyBoard = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
|
||||||
|
|
||||||
const textFromBoard = board =>
|
const textFromBoard = board =>
|
||||||
` ${board[0]} | ${board[1]} | ${board[2]} \n` +
|
` ${board[0]} | ${board[1]} | ${board[2]} \n` +
|
||||||
`-----------\n` +
|
'-----------\n' +
|
||||||
` ${board[3]} | ${board[4]} | ${board[5]} \n` +
|
` ${board[3]} | ${board[4]} | ${board[5]} \n` +
|
||||||
`-----------\n` +
|
'-----------\n' +
|
||||||
` ${board[6]} | ${board[7]} | ${board[8]}`
|
` ${board[6]} | ${board[7]} | ${board[8]}`
|
||||||
|
|
||||||
const numEmojis = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
|
const numEmojis = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
|
||||||
|
@ -21,7 +21,7 @@ const winningThrees = [
|
||||||
[2, 5, 8],
|
[2, 5, 8],
|
||||||
|
|
||||||
[0, 4, 8],
|
[0, 4, 8],
|
||||||
[2, 4, 6],
|
[2, 4, 6]
|
||||||
]
|
]
|
||||||
|
|
||||||
const checkWinner = board => {
|
const checkWinner = board => {
|
||||||
|
@ -71,4 +71,3 @@ routine.build({
|
||||||
makeMove: applyTurn,
|
makeMove: applyTurn,
|
||||||
checkWinner
|
checkWinner
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
10
src/index.js
10
src/index.js
|
@ -26,12 +26,12 @@ onTempChangeRequested(change => {
|
||||||
case 'Hotter': {
|
case 'Hotter': {
|
||||||
lowTemp += 2
|
lowTemp += 2
|
||||||
highTemp += 2
|
highTemp += 2
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
case 'Colder': {
|
case 'Colder': {
|
||||||
lowTemp -= 2
|
lowTemp -= 2
|
||||||
highTemp -= 2
|
highTemp -= 2
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
case 'Good': {
|
case 'Good': {
|
||||||
return
|
return
|
||||||
|
@ -42,9 +42,9 @@ onTempChangeRequested(change => {
|
||||||
lowTemp = cleanTemp(lowTemp)
|
lowTemp = cleanTemp(lowTemp)
|
||||||
|
|
||||||
const mode =
|
const mode =
|
||||||
indoorTemperature < lowTemp ? heatMode : // Heat if lower than low
|
indoorTemperature < lowTemp ? heatMode // Heat if lower than low
|
||||||
indoorTemperature > highTemp ? coolMode : // Cool if hotter than high
|
: indoorTemperature > highTemp ? coolMode // Cool if hotter than high
|
||||||
change === 'Hotter' ? heatMode : coolMode // Otherwise (lower priority) follow the requested change
|
: change === 'Hotter' ? heatMode : coolMode // Otherwise (lower priority) follow the requested change
|
||||||
|
|
||||||
if (!mode) {
|
if (!mode) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
const { App: SlackApp } = require('@slack/bolt')
|
const { App: SlackApp } = require('@slack/bolt')
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
|
const { addReactions } = require('../games/hvacoins/utils')
|
||||||
|
|
||||||
const temperatureChannelId = 'C034156CE03'
|
const temperatureChannelId = 'C034156CE03'
|
||||||
const hvackerBotUserId = 'U0344TFA7HQ'
|
|
||||||
const sageUserId = 'U028BMEBWBV'
|
|
||||||
|
|
||||||
const pollingMinutes = 5
|
const pollingMinutes = 5
|
||||||
const pollingPeriod = 1000 * 60 * pollingMinutes
|
const pollingPeriod = 1000 * 60 * pollingMinutes
|
||||||
|
@ -41,6 +40,7 @@ const sendHelp = async (say, prefix) => {
|
||||||
await say({
|
await say({
|
||||||
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' +
|
||||||
'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 Quade.'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -58,22 +58,38 @@ app.event('reaction_added', async ({ event, context, client, say }) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const ourUsers = {
|
const users = {
|
||||||
U028BMEBWBV: 'Sage',
|
U028BMEBWBV: 'Sage',
|
||||||
U02U15RFK4Y: 'Adam',
|
U02U15RFK4Y: 'Adam',
|
||||||
U02AAB54V34: 'Houston',
|
U02AAB54V34: 'Houston',
|
||||||
U02KYLVK1GV: 'Quade',
|
U02KYLVK1GV: 'Quade',
|
||||||
U017PG4EL1Y: 'Max',
|
U017PG4EL1Y: 'Max',
|
||||||
UTDLFGZA5: 'Tyler',
|
UTDLFGZA5: 'Tyler',
|
||||||
U017CB5L1K3: 'Andres'
|
U017CB5L1K3: 'Andres',
|
||||||
|
U0344TFA7HQ: 'Hvacker',
|
||||||
|
U0X0ZQCN6: 'Caleb',
|
||||||
|
U03BBTD4CQZ: 'Fernando',
|
||||||
|
|
||||||
|
Sage: 'U028BMEBWBV',
|
||||||
|
Adam: 'U02U15RFK4Y',
|
||||||
|
Houston: 'U02AAB54V34',
|
||||||
|
Quade: 'U02KYLVK1GV',
|
||||||
|
Max: 'U017PG4EL1Y',
|
||||||
|
Tyler: 'UTDLFGZA5',
|
||||||
|
Andres: 'U017CB5L1K3',
|
||||||
|
Caleb: 'U0X0ZQCN6',
|
||||||
|
Hvacker: 'U0344TFA7HQ',
|
||||||
|
Fernando: 'U03BBTD4CQZ',
|
||||||
}
|
}
|
||||||
|
|
||||||
const activePolls = {}
|
const activePolls = {}
|
||||||
const testId = 'U028BMEBWBV_TEST'
|
const testId = 'U028BMEBWBV_TEST'
|
||||||
let testMode = false
|
let testMode = false
|
||||||
app.event('message', async ({ event, context, client, say }) => {
|
app.event('message', async ({ event, context, client, say }) => {
|
||||||
console.log(event)
|
if (event.subtype !== 'message_changed') {
|
||||||
if (event.user === sageUserId) {
|
console.log(event)
|
||||||
|
}
|
||||||
|
if (event?.user === users.Sage) {
|
||||||
if (event?.text.startsWith('!')) {
|
if (event?.text.startsWith('!')) {
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
await messageSage('Currently in test mode!')
|
await messageSage('Currently in test mode!')
|
||||||
|
@ -89,13 +105,14 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
event.user = testId
|
event.user = testId
|
||||||
}
|
}
|
||||||
// console.log(event.blocks[0].elements[0])
|
|
||||||
}
|
}
|
||||||
for (const listener of messageListeners) {
|
for (const listener of messageListeners) {
|
||||||
listener({ event, say })
|
listener({ event, say })
|
||||||
}
|
}
|
||||||
console.log('MSG', ourUsers[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
|
if (event.user) {
|
||||||
if (event.user === 'U028BMEBWBV' && event.channel === 'D0347Q4H9FE') {
|
console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
|
||||||
|
}
|
||||||
|
if (event.user === users.Sage && event.channel === 'D0347Q4H9FE') {
|
||||||
if (event.text === '!!kill') {
|
if (event.text === '!!kill') {
|
||||||
process.exit()
|
process.exit()
|
||||||
}
|
}
|
||||||
|
@ -103,18 +120,10 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
await postToTechThermostatChannel(event.text.substring(4).trim())
|
await postToTechThermostatChannel(event.text.substring(4).trim())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (event.text?.startsWith('!saytoq ')) {
|
|
||||||
await messageQuade(event.text.substring(7).trim())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.text?.startsWith('!saytos')) {
|
|
||||||
await messageSage(event.text.substring(7).trim())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const eventText = event.text?.toLowerCase() || ''
|
const eventText = event.text?.toLowerCase() || ''
|
||||||
|
|
||||||
if (eventText.startsWith('!help')) {
|
if (eventText === '!help') {
|
||||||
await sendHelp(say)
|
await sendHelp(say)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -139,21 +148,51 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
timestamp: pollTs,
|
timestamp: pollTs,
|
||||||
full: true
|
full: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reactPosters = {}
|
||||||
|
reactions.message.reactions.forEach(r => r.users.forEach(user => {
|
||||||
|
reactPosters[user] ??= []
|
||||||
|
reactPosters[user].push(r.name)
|
||||||
|
}))
|
||||||
|
|
||||||
const reactCounts = {}
|
const reactCounts = {}
|
||||||
reactions.message.reactions.forEach(reaction => { reactCounts[reaction.name] = reaction.count })
|
Object.entries(reactPosters).forEach(([id, votes]) => {
|
||||||
|
votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].includes(v))
|
||||||
|
if (votes.length === 1) {
|
||||||
|
reactCounts[votes[0]] ??= 0
|
||||||
|
reactCounts[votes[0]] += 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const contentVotes = reactCounts[goodEmoji]
|
const contentVotes = reactCounts[goodEmoji] || 0
|
||||||
const hotterVotes = reactCounts[hotterEmoji]
|
let hotterVotes = reactCounts[hotterEmoji] || 0
|
||||||
const colderVotes = reactCounts[colderEmoji]
|
let colderVotes = reactCounts[colderEmoji] || 0
|
||||||
|
console.log('before contentVotes', contentVotes)
|
||||||
|
console.log('before colderVotes', colderVotes)
|
||||||
|
console.log('before hotterVotes', hotterVotes)
|
||||||
|
|
||||||
let text = 'The people have spoken, and would like to '
|
if (hotterVotes > colderVotes) {
|
||||||
|
hotterVotes -= colderVotes
|
||||||
|
colderVotes = 0
|
||||||
|
} else if (colderVotes > hotterVotes) {
|
||||||
|
colderVotes -= hotterVotes
|
||||||
|
hotterVotes = 0
|
||||||
|
}
|
||||||
|
console.log('after contentVotes', contentVotes)
|
||||||
|
console.log('after colderVotes', colderVotes)
|
||||||
|
console.log('after hotterVotes', hotterVotes)
|
||||||
|
|
||||||
|
let text
|
||||||
if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
|
if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
|
||||||
|
text = `<@${users.Quade}> 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.Quade}> 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 {
|
||||||
|
text = `The people have spoken, and would like to `
|
||||||
text += 'keep the temperature as-is, quaaack.'
|
text += 'keep the temperature as-is, quaaack.'
|
||||||
requestTempChange('Good')
|
requestTempChange('Good')
|
||||||
}
|
}
|
||||||
|
@ -166,9 +205,6 @@ app.event('message', async ({ event, context, client, say }) => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await app.start().catch(console.error)
|
await app.start().catch(console.error)
|
||||||
console.log('Slack Bolt has started')
|
console.log('Slack Bolt has started')
|
||||||
// setTimeout(async () => {
|
|
||||||
// await messageSage('<https://i.imgur.com/VCvfvdz.png|...>')
|
|
||||||
// }, 2000)
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const postToTechThermostatChannel = async optionsOrText => {
|
const postToTechThermostatChannel = async optionsOrText => {
|
||||||
|
@ -180,8 +216,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(sageUserId, optionsOrText)
|
const messageSage = async optionsOrText => messageIn(users.Sage, optionsOrText)
|
||||||
const messageQuade = async optionsOrText => messageIn('U02KYLVK1GV', optionsOrText)
|
|
||||||
|
|
||||||
const messageIn = async (channel, optionsOrText) => {
|
const messageIn = async (channel, optionsOrText) => {
|
||||||
if (optionsOrText === null || typeof optionsOrText !== 'object') {
|
if (optionsOrText === null || typeof optionsOrText !== 'object') {
|
||||||
|
@ -196,17 +231,14 @@ const startPoll = async () => {
|
||||||
const sent = await postToTechThermostatChannel({
|
const sent = await postToTechThermostatChannel({
|
||||||
text: `<!here|here> Temperature poll requested! In ${pollingMinutes} minutes the temperature will be adjusted.\n` +
|
text: `<!here|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 Quade do it!)'
|
||||||
|
})
|
||||||
|
await addReactions({
|
||||||
|
app,
|
||||||
|
channelId: temperatureChannelId,
|
||||||
|
timestamp: sent.ts,
|
||||||
|
reactions: [colderEmoji, hotterEmoji, goodEmoji]
|
||||||
})
|
})
|
||||||
const addReaction = async emojiName =>
|
|
||||||
app.client.reactions.add({
|
|
||||||
channel: temperatureChannelId,
|
|
||||||
timestamp: sent.ts,
|
|
||||||
name: emojiName
|
|
||||||
})
|
|
||||||
await addReaction(colderEmoji)
|
|
||||||
await addReaction(hotterEmoji)
|
|
||||||
await addReaction(goodEmoji)
|
|
||||||
return sent.ts
|
return sent.ts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,19 +266,18 @@ const decodeData = (key, message) => {
|
||||||
const onReaction = listener => reactionListeners.push(listener)
|
const onReaction = listener => reactionListeners.push(listener)
|
||||||
|
|
||||||
onReaction(async ({ event }) => {
|
onReaction(async ({ event }) => {
|
||||||
if (event.user === sageUserId && event.reaction === 'x') {
|
if (event.user === users.Sage) {
|
||||||
console.log(event)
|
if (event.reaction === 'x') {
|
||||||
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) {
|
||||||
console.error(e)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app,
|
app,
|
||||||
hvackerBotUserId,
|
|
||||||
temperatureChannelId,
|
temperatureChannelId,
|
||||||
onAction: app.action,
|
onAction: app.action,
|
||||||
getMessage,
|
getMessage,
|
||||||
|
@ -257,10 +288,9 @@ module.exports = {
|
||||||
onReaction,
|
onReaction,
|
||||||
encodeData,
|
encodeData,
|
||||||
decodeData,
|
decodeData,
|
||||||
sageUserId,
|
|
||||||
messageSage,
|
messageSage,
|
||||||
messageIn,
|
messageIn,
|
||||||
testMode,
|
testMode,
|
||||||
testId,
|
testId,
|
||||||
ourUsers
|
users
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue