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
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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) {
|
||||||
console.log('SAVING GAME')
|
if (after) {
|
||||||
|
console.log(`SAVING GAME after ${after}`)
|
||||||
|
} else {
|
||||||
|
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,
|
users[userId].items ??= {}
|
||||||
items: {},
|
users[userId].upgrades ??= {}
|
||||||
upgrades: {},
|
users[userId].achievements ??= {}
|
||||||
achievements: {},
|
users[userId].coinsAllTime ??= users[userId].coins
|
||||||
coinsAllTime: 0,
|
users[userId].prestige ??= 0
|
||||||
prestige: 0
|
users[userId].startDate ??= new Date()
|
||||||
}
|
if (updateCoins) {
|
||||||
} else {
|
users[userId].coins = getCoins(userId)
|
||||||
users[userId].items ??= {}
|
|
||||||
users[userId].upgrades ??= {}
|
|
||||||
users[userId].achievements ??= {}
|
|
||||||
users[userId].coinsAllTime ??= users[userId].coins
|
|
||||||
users[userId].prestige ??= 0
|
|
||||||
}
|
}
|
||||||
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 => {
|
||||||
await slack.app.client.reactions.add({
|
try {
|
||||||
channel,
|
await slack.app.client.reactions.add({
|
||||||
timestamp: ts,
|
channel,
|
||||||
name: emojiName
|
timestamp: ts,
|
||||||
})
|
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') {
|
||||||
const eventText = event.text?.toLowerCase()
|
return;
|
||||||
if (eventText && startTriggers.find(keyword => eventText.startsWith('!' + keyword))) {
|
}
|
||||||
console.log('Trigger found')
|
const eventText = event.text?.toLowerCase()
|
||||||
const opponent = event.text.toUpperCase().match(/<@[^>]*>/)[0]
|
if (!(eventText && startTriggers.find(keyword => eventText.startsWith('!' + keyword)))) {
|
||||||
console.log('Messaging opponent ' + slack.users[opponent.substring(2, opponent.length - 1)])
|
return;
|
||||||
const msg = messageFromBoard({
|
}
|
||||||
dataName,
|
try {
|
||||||
gameName,
|
console.log('Trigger found')
|
||||||
textFromBoard,
|
const opponent = event.text.toUpperCase().match(/<@[^>]*>/)[0]
|
||||||
board: initialBoard(),
|
console.log('Messaging opponent ' + slack.users[opponent.substring(2, opponent.length - 1)])
|
||||||
player1: '<@' + event.user + '>',
|
const channelMap = {}
|
||||||
player2: opponent
|
const msg = () => messageFromBoard({
|
||||||
})
|
dataName,
|
||||||
const sent = await say(msg)
|
gameName,
|
||||||
await addChoiceEmojis({ ...sent, choices: turnChoiceEmojis })
|
textFromBoard,
|
||||||
|
board: initialBoard(),
|
||||||
|
player1: '<@' + event.user + '>',
|
||||||
|
player2: opponent,
|
||||||
|
channelMap
|
||||||
|
})
|
||||||
|
const sent = await say(msg())
|
||||||
|
channelMap[event.user] = {
|
||||||
|
channel: sent.channel,
|
||||||
|
ts: sent.ts
|
||||||
}
|
}
|
||||||
|
await updateAll({ name: gameName, text: msg(), add: sent })
|
||||||
|
await addChoiceEmojis({...sent, choices: turnChoiceEmojis})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const encodeGame = (dataKey, board, players) => slack.encodeData(dataKey, { board, players })
|
const encodeGame = (dataKey, board, players, channelMap = {}) => slack.encodeData(dataKey, { board, players, channelMap })
|
||||||
|
|
||||||
const decodeGame = (dataKey, message) => slack.decodeData(dataKey, message)
|
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...')
|
slack.app.client.reactions.remove({
|
||||||
|
channel,
|
||||||
|
timestamp: ts,
|
||||||
|
name: emojiName
|
||||||
|
}))
|
||||||
|
turnChoiceEmojis.forEach(removeEmoji)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
const winnerMessages = getMessages(winner)
|
||||||
|
// await say(boardMessage() + winnerMessages.you)
|
||||||
|
console.log('TurnHandler', { gameName, boardMessage: boardMessage() })
|
||||||
|
// await updateAll({ name: gameName, text: boardMessage() + '\nTurnHandler' })
|
||||||
|
// if (!winner) {
|
||||||
|
// await say('Waiting for opponent\'s response...')
|
||||||
|
// }
|
||||||
|
|
||||||
const removeEmoji = async emojiName =>
|
// const removeEmoji = async emojiName =>
|
||||||
slack.app.client.reactions.remove({
|
// slack.app.client.reactions.remove({
|
||||||
channel: event.item.channel,
|
// channel: event.item.channel,
|
||||||
timestamp: message.messages[0]?.ts,
|
// timestamp: message.messages[0]?.ts,
|
||||||
name: emojiName
|
// name: emojiName
|
||||||
})
|
// })
|
||||||
turnChoiceEmojis.forEach(removeEmoji)
|
// turnChoiceEmojis.forEach(removeEmoji)
|
||||||
console.log('SENDING to ' + opponent)
|
console.log('SENDING to ' + opponent)
|
||||||
const sentBoard = await slack.app.client.chat.postMessage({
|
if (!channelMap[opponent]) {
|
||||||
channel: opponent,
|
const sentBoard = await slack.app.client.chat.postMessage({
|
||||||
text: boardMessage + winnerMessages.opponent
|
channel: 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,25 +288,44 @@ 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) => {
|
||||||
const regex = new RegExp(`http://${key}ZZZ[^|]*`)
|
try {
|
||||||
let match = message.match(regex)
|
const regex = new RegExp(`http://${key}ZZZ[^|]*`)
|
||||||
if (!match) {
|
let match = message.match(regex)
|
||||||
return match
|
if (!match) {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length
|
||||||
|
return JSON.parse(Buffer.from(match, 'base64').toString('utf-8'))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length
|
|
||||||
return JSON.parse(Buffer.from(match, 'base64').toString('utf-8'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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