Several fixes and additions:

Add Nik.
Consider removing Tyler but don't.
Fix skin-color temp votes.
Fix prestige gating.
Tweak !mine odds.
Pass squadgrades to upgrade condition-checking.
Fix amount parsing in edge cases.
Add changelog.
New quackgrade.
Upgrades re-org.
Add evil/heavenly upgrades dependent on squadgrades.
Add minimum to chaosFilter.
This commit is contained in:
Sage Vaillancourt 2022-05-03 09:56:10 -04:00
parent 4e15721803
commit 6dabe9d85a
5 changed files with 353 additions and 65 deletions

View File

@ -15,7 +15,7 @@ const {
getRandomFromArray,
chaosFilter,
addReactions,
game: { nfts, squad, users, horrors }, setHighestCoins, definitelyShuffle
game: { nfts, squad, users, horrors }, setHighestCoins, definitelyShuffle, getCompletedSquadgradeNames
} = require('./utils')
const slack = require('../../slack')
const buyableItems = require('./buyableItems')
@ -24,7 +24,7 @@ const achievements = require('./achievements')
const webapi = require('./webapi')
const prestige = require('./prestige')
const lore = require('./lore')
const { getChaos } = require('./quackstore')
const { getChaos, quackStore } = require('./quackstore')
const settings = require('./settings')
// const readline = require('readline').createInterface({
@ -44,7 +44,7 @@ const settings = require('./settings')
const getUpgradeEmoji = upgrade => upgrade.emoji || buyableItems[upgrade.type].emoji
const upgradeText = (user, showOwned = false) => {
const userDoesNotHave = ([upgradeName, upgrade]) => hasUpgrade(user, upgrade, upgradeName) === showOwned
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user)
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
const format = ([key, value]) => `:${getUpgradeEmoji(value)}: *${key}* - ${commas(value.cost)}\n_${value.description}_`
return '\n\n' +
Object.entries(upgrades)
@ -86,7 +86,7 @@ const dmsOnly = {
const prestigeOnly = {
hidden: false,
condition: async ({ event, say, words, user }) => {
if (user.prestige) {
if (!user.prestige) {
await say('Sorry, you must have prestiged to access this menu.')
}
return user.prestige && await dmsOnly.condition({ event, say, words, user })
@ -287,7 +287,13 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
await c.action({ event, say, trueSay, words, user, userId: event.user })
if (!isRecycle) {
setTimeout(() => lightning({ event, say, trueSay, words, user }), 10000)
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
const defaultOdds = 0.01
const odds = userQuackgrades.reduce((total, upgrade) => upgrade.effect(total), defaultOdds)
console.log(odds)
if (Math.random() < odds) {
setTimeout(() => lightning({ event, say, trueSay, words, user }), 10000)
}
}
}
@ -327,13 +333,8 @@ slack.onReaction(async ({ event }) => {
})
const strikes = {}
const lightning = async ({ event }) => {
if (Math.random() > 0.01) {
return
}
if (event.user !== slack.users.Sage) {
await slack.messageSage(`Sent a zap to ${slack.users[event.user]}.`)
}
const lightning = async ({ event, user }) => {
const msToBottle = chaosFilter(5000, 1, user, Infinity, 2500)
const message = await slack.app.client.chat.postMessage({
channel: event.channel,
text: ':zap: Lightning strike!',
@ -370,7 +371,7 @@ const lightning = async ({ event }) => {
blocks: []
})
await slack.messageSage(`They didn't bottle it!`)
}, 5000)
}, msToBottle)
}
slack.app.action('lightningStrike', async ({ body, ack }) => {
@ -380,20 +381,20 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
}
delete strikes[body.message.ts]
const c = getCoins(body.user.id)
const secondsOfCps = seconds => Math.floor(getCPS(getUser(body.user.id)) * seconds)
const diff = 500 + Math.floor(c * 0.10) + secondsOfCps(60 * 30)
const user = getUser(body.user.id)
user.coins += diff
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
let payout = Math.floor(c * 0.10) + secondsOfCps(60 * 30)
payout = 500 + chaosFilter(payout, 1, user)
user.coins = c + payout
saveGame()
await slack.app.client.chat.update({
channel: body.channel.id,
ts: body.message.ts,
text: `Lightning successfully bottled! :sake::zap: You got ${commas(diff)} HVAC!`,
text: `Lightning successfully bottled! :sake::zap: You got ${commas(payout)} HVAC!`,
blocks: []
})
await ack()
await slack.messageSage(`They bottled it!`)
})
slack.onMessage(async msg => {
@ -486,15 +487,6 @@ command(
timestamp: sent.ts,
reactions: ['one', 'two', 'three']
})
// const addReaction = async emojiName =>
// slack.app.client.reactions.add({
// channel: event.channel,
// timestamp: sent.ts,
// name: emojiName
// })
// await addReaction('one')
// await addReaction('two')
// await addReaction('three')
const ts = sent.message.ts
const lastOne = {}
@ -686,10 +678,9 @@ command(['!cps'],
say(`You are currently earning \`${commas(getCPS(user))}\` HVAC Coin per second.`))
// What's that one game? Split or Steal?
command(['!split'],
`Play a game of split or steal.`
, {hidden: true})
// command(['!split'],
// `Play a game of split or steal.`
// , {hidden: true})
// const maxTemp = 30
@ -749,16 +740,16 @@ const doMine = async ({ user, userId, say }) => {
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
let diff
let prefix
if (random > 0.9967) {
if (random > 0.9937) {
diff = 500 + Math.floor(c * 0.10) + secondsOfCps(60 * 30)
prefix = `:gem: You found a lucky gem worth ${commas(diff)} HVAC!\n`
addAchievement(user, 'luckyGem', say)
await slack.messageSage(`${slack.users[userId]} FOUND A LUCKY GEM COIN WORTH ${commas(diff)} HVAC!`)
} else if (random > 0.986) {
} else if (random > 0.976) {
diff = 50 + Math.floor(c * 0.025) + secondsOfCps(60)
prefix = `:goldbrick: You found a lucky gold coin worth ${commas(diff)} HVAC!\n`
addAchievement(user, 'goldBrick', say)
} else if (random > 0.96) {
} else if (random > 0.95) {
diff = 10 + Math.floor(c * 0.01) + secondsOfCps(10)
prefix = `:money_with_wings: You found a lucky green coin worth ${commas(diff)} HVAC!\n`
addAchievement(user, 'greenCoin', say)
@ -882,7 +873,7 @@ command(
if (hasUpgrade(user, upgrade, upgradeName)) {
return say('You already have that upgrade!')
}
if (!upgrade.condition(user)) {
if (!upgrade.condition(user, getCompletedSquadgradeNames())) {
return say('That item does not exist!')
}
const c = user.coins
@ -941,7 +932,8 @@ command(
return say('No squadgrades are currently available')
}
const currentCoins = user.coins
let amount = parseAll(words[1], currentCoins)
let [, ...amountWords] = words
let amount = parseAll(amountWords.join(' '), currentCoins)
if (amount > currentCoins) {
return say(`You don't have that much HVAC! You have ${currentCoins}.`)
}
@ -953,11 +945,17 @@ command(
}
squad.upgrades[current.name] -= amount
user.coins -= amount
if (squad.upgrades[current.name] <= 0) {
user.squadGradeContributions ??= 0
user.squadGradeContributions += amount
let status
if (squad.upgrades[current.name] < 1) {
squad.upgrades[current.name] = true
status = `\n\nYou now have ${current.name}!`
} else {
status = ` Current status:\n\n${squadText()}`
}
saveGame()
await say(`Thank you for your contribution of ${commas(amount)} HVAC! Current status:\n\n${squadText()}`)
await say(`Thank you for your contribution of ${commas(amount)} HVAC!${status}`)
}
)
@ -970,6 +968,22 @@ command(
buyRoute
)
command(
['!changelog', '!changes'],
`View my current git log`,
async ({ event, }) => {
const command = `git log > ./gitlog`
const child = exec(command)
child.on('close', async () => {
await slack.app.client.files.upload({
channels: event.channel,
initial_comment: 'Here\'s my current `git log`',
file: createReadStream('./gitlog')
})
})
}
)
command(
['!check', '!ch'],
'Check how many coins another player has',
@ -994,7 +1008,8 @@ command(
'Donate coins to a fellow player\n' +
' Send coins by saying \'!gift @player coin_amount\'',
async ({ words, say, user }) => {
const [, target, amountText] = words
let [, target, ...amountText] = words
amountText = amountText.join(' ')
const amount = parseAll(amountText, user.coins)
const targetId = idFromWord(target)
if (!amount || amount < 0) {
@ -1006,6 +1021,10 @@ command(
if (user.coins < amount) {
return say(`You don't have that many coins! You have ${commas(user.coins)} HVAC.`)
}
const date = new Date()
if (targetId === slack.users.Nik && date.getFullYear() === 2022 && date.getMonth() === 4) {
return say(`Your generosity is appreciated, but let the guy play for a bit!`)
}
if (amountText === 'all' && slack.users.Tyler === targetId) {
addAchievement(user, 'walmartGiftCard', say)
}
@ -1091,7 +1110,7 @@ command(
let index = 1
await say(
Object.entries(users)
.filter(([id, user]) => Object.entries(user.items).length > 0)
.filter(([id, user]) => Object.entries(user.items).length > 0 || user.prestige)
.sort(([id, user1], [id2, user2]) => {
const leftPrestige = getUser(id).prestige
const rightPrestige = getUser(id2).prestige
@ -1213,6 +1232,7 @@ command(
const exec = require('child_process').exec
const fs = require('fs')
const { createReadStream } = require('fs')
command(
['!pl'],
'!pl `code`\n\n' +

View File

@ -37,6 +37,17 @@ const quackStore = {
return cps * getChaos(Math.round(user.interactions / 50))
},
cost: 10
},
dryerSheet: {
name: 'Dryer Sheet',
type: 'lightning',
emoji: 'rose',
description: 'Smells nice. Makes lightning twice as likely to strike.',
//+ '_\n_Averages a 26% CPS boost.',
effect: lightningOdds => lightningOdds * 2,
preReqs: ['nuclearFuel'],
cost: 10
}
}

View File

@ -1,9 +1,49 @@
const basic = ({ type, description, count, cost }) => ({
const basic = ({ type, description, count, cost, extraCondition = () => true, effect = cps => cps * 2 }) => ({
type,
description,
condition: user => user.items[type] >= count,
condition: (user, squadGrades) => user.items[type] >= count && extraCondition(user, squadGrades),
cost,
effect: itemCps => itemCps * 2
effect
})
const evil = ({ type, description, cost }) => basic({
type,
description,
count: 40,
cost,
extraCondition: (user, squadGrades) => squadGrades?.includes('discardHumanMorals'),
})
const heavenly = ({ type, description, cost }) => basic({
type,
description,
count: 60,
cost,
extraCondition: (user, squadGrades) => squadGrades?.includes('redemption'),
})
const baby = ({ type, description, cost }) => basic({
type,
description,
count: 80,
cost,
extraCondition: (user, squadGrades) => squadGrades?.includes('redemption'),
})
const geometry = ({ type, description, cost }) => basic({
type,
description,
count: 100,
cost,
extraCondition: (user, squadGrades) => squadGrades?.includes('redemption'),
})
const universitality = ({ type, description, cost }) => basic({
type,
description,
count: 100,
cost,
extraCondition: (user, squadGrades) => squadGrades?.includes('redemption'),
})
module.exports = {
@ -25,24 +65,44 @@ module.exports = {
count: 25,
cost: 50_000
}),
rats: evil({
type: 'mouse',
description: 'Consume the rotten remains of your foes',
cost: 150_000,
}),
hoodedMice: heavenly({
type: 'mouse',
description: 'These monks have nearly reached enlightenment.',
cost: 1_000_000,
}),
fasterComputers: basic({
type: 'accountant',
description: 'Accountants can ~steal~ optimize twice as much HVAC!',
count: 1,
cost: 11_000
cost: 11_000,
}),
lackOfMorality: basic({
type: 'accountant',
description: 'Accountants are taking a hint from nearby CEOs.',
count: 10,
cost: 200_000
cost: 200_000,
}),
widerBrains: basic({
type: 'accountant',
description: 'For accountant do double of thinking.',
count: 25,
cost: 550_000
cost: 550_000,
}),
vastLayoffs: evil({
type: 'accountant',
description: 'The weak are not part of our future.',
cost: 2_450_000,
}),
charityFund: heavenly({
type: 'accountant',
description: 'THIS one is more than just a tax break.',
cost: 16_333_333,
}),
biggerBlowhole: basic({
@ -63,6 +123,16 @@ module.exports = {
count: 25,
cost: 6_000_000
}),
blightWhales: evil({
type: 'whale',
description: `Infectious with evil, they swim the ocean spreading their spores.`,
cost: 24_000_000
}),
whaleChoir: heavenly({
type: 'whale',
description: `Their cleansing songs reverberate through the sea.`,
cost: 144_000_000
}),
greasyTracks: basic({
type: 'train',
@ -82,6 +152,16 @@ module.exports = {
count: 25,
cost: 65_000_000
}),
hellTrain: evil({
type: 'train',
description: 'Shipping blood needed for the ritual.',
cost: 370_000_000
}),
toyTrain: heavenly({
type: 'train',
description: 'Something simple. Toot toot!',
cost: 2_220_000_000
}),
gasolineFire: basic({
type: 'fire',
@ -101,6 +181,16 @@ module.exports = {
count: 25,
cost: 700_000_000
}),
lava: evil({
type: 'fire',
description: `Hopefully no usurpers have any "accidents".`,
cost: 4_200_000_000
}),
blueFire: heavenly({
type: 'fire',
description: `You can hear it singing with delight.`,
cost: 25_200_000_000
}),
spoonerang: basic({
type: 'boomerang',
@ -120,6 +210,16 @@ module.exports = {
count: 25,
cost: 10_000_000_000
}),
loyalRang: evil({
type: 'boomerang',
description: `Frequently reports back to your throne on the state of your empire.`,
cost: 60_000_000_000
}),
youRang: heavenly({
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.',
cost: 360_000_000_000
}),
lunarPower: basic({
type: 'moon',
@ -139,6 +239,16 @@ module.exports = {
count: 25,
cost: 165_000_000_000
}),
tidalUpheaval: evil({
type: 'moon',
description: `The hell with the ocean. That's valuable_ *the abstract concept of more power* _we're losing.`,
cost: 865_000_000_000
}),
newMoon: heavenly({
type: 'moon',
description: `Build a second moon to provide space for affordable housing.`,
cost: 5_190_000_000_000
}),
glassButterfly: basic({
type: 'butterfly',
@ -158,6 +268,16 @@ module.exports = {
count: 25,
cost: 2_550_000_000_000
}),
venomousMoths: evil({
type: 'butterfly',
description: 'Specifically manufactured for their horrifying brain-melt toxins.',
cost: 12_550_000_000_000
}),
quietingNectar: heavenly({
type: 'butterfly',
description: 'Calming and extra sweet. Soothes even human ails.',
cost: 75_300_000_000_000
}),
silverMirror: basic({
type: 'mirror',
@ -177,6 +297,16 @@ module.exports = {
count: 25,
cost: 37_500_000_000_000
}),
crackedMirror: evil({
type: 'mirror',
description: `YOU SMILE. DO NOT FEAR, THIS IS THE FACE OF A FRIEND.`,
cost: 222_000_000_000_000
}),
funHouseMirror: heavenly({
type: 'mirror',
description: `yoU LOok so siLLY IN thesE THINgs`,
cost: 1_330_000_000_000_000
}),
fzero: basic({
type: 'quade',
@ -196,6 +326,16 @@ module.exports = {
count: 25,
cost: 500_000_000_000_000
}),
thatsNotQuade: evil({
type: 'quade',
description: `The skinless face lacks even a moustache. Nevertheless, it pledges its allegiance.`,
cost: 3_000_000_000_000_000
}),
hannahMontanaLinux: heavenly({
type: 'quade',
description: `The patrician's choice.`,
cost: 18_000_000_000_000_000
}),
latestNode: basic({
type: 'hvacker',
@ -215,6 +355,103 @@ module.exports = {
count: 25,
cost: 7_000_000_000_000_000
}),
undefinedBehavior: evil({
type: 'hvacker',
description: `skREEEFDS☐☐☐☐☐it's☐jwtoo☐laate☐☐☐☐☐`,
cost: 42_000_000_000_000_000
}),
mutualUnderstanding: heavenly({
type: 'hvacker',
description: `lol fat chance, dummy. Points for trying, though`,
cost: 250_000_000_000_000_000
}),
coffee: basic({
type: 'creator',
description: `Didn't you know? It makes you smarter. No consequencAAAAAA`,
count: 1,
cost: 1_960_000_000_000_000
}),
bribery: basic({
type: 'creator',
description: `How much could he be making that a couple bucks won't get me more HVAC?`,
count: 10,
cost: 32_300_000_000_000_000
}),
vim: basic({
type: 'creator',
description: `*teleports behind you*`,
count: 25,
cost: 100_000_000_000_000_000
}),
regrets: evil({
type: 'creator',
description: `HE HAS NONE. HE LAUGHS.`,
cost: 600_000_000_000_000_000
}),
goVegan: heavenly({
type: 'creator',
description: `Unlock your vegan powers.`,
cost: 3_600_000_000_000_000_000
}),
angelInvestors: basic({
type: 'smallBusiness',
description: 'Not so small NOW are we?',
count: 1,
cost: 3_140_000_000_000_000
}),
officeManager: basic({
type: 'smallBusiness',
description: 'Sate your laborers with snacks.',
count: 10,
cost: 80_000_000_000_000_000
}),
undyingLoyalty: basic({
type: 'smallBusiness',
description: 'Your foolish employees bow to your every whim, regardless of salary.',
count: 25,
cost: 138_000_000_000_000_000
}),
deathSquad: evil({
type: 'smallBusiness',
description: `pwease don't unionize uwu :pleading_face:`,
cost: 858_000_000_000_000_000
}),
coop: heavenly({
type: 'smallBusiness',
description: `By the people, for the people.`,
cost: 5_140_000_000_000_000_000
}),
corporateBuyouts: basic({
type: 'bigBusiness',
description: 'The cornerstone of any family-run business.',
count: 1,
cost: 28_140_000_000_000_000
}),
politicalSway: basic({
type: 'bigBusiness',
description: `What's a bit of lobbying between friends?`,
count: 10,
cost: 560_000_000_000_000_000
}),
humanDiscontent: basic({
type: 'bigBusiness',
description: 'A sad populace is a spendy populace!',
count: 25,
cost: 1_372_000_000_000_000_000
}),
weJustKillPeopleNow: evil({
type: 'bigBusiness',
description: 'It is extremely difficult to get more evil than we already were. Nevertheless,',
cost: 7_072_000_000_000_000_000
}),
makePublic: heavenly({
type: 'bigBusiness',
description: `Downplay immediate profit for more long-term benefits.`,
cost: 42_000_000_000_000_000_000
}),
homage: {
type: 'general',
@ -232,13 +469,4 @@ module.exports = {
cost: 100_000_000_000_000,
effect: (itemCps, user) => itemCps * 1.1
}
// moreUpgrades: {
// type: 'general',
// description: 'Adds additional upgrades',
// condition: user => Object.entries(user.items).reduce((total, [, countOwned]) => countOwned + total, 0) >= 400,
// emoji: 'cookie',
// cost: 10_000_000_000_000,
// effect: nothing
// },
}

View File

@ -21,16 +21,19 @@ const loadGame = () => {
return game
}
const chaosFilter = (num, odds, user, max = Infinity) => {
const chaosFilter = (num, odds, user, max = Infinity, min = -Infinity) => {
const userQuackgrades = user.quackUpgrades?.cps || []
const hasChaos = userQuackgrades.includes('chaos')
if (!hasChaos || Math.random() < odds) {
if (!hasChaos || Math.random() < odds || !num) {
return num
}
const chaosed = num * getChaos(user)
if (chaosed > max) {
return max
}
if (chaosed < min) {
return min
}
return chaosed
}
@ -112,11 +115,13 @@ const parseAll = (str, allNum) => {
str = str.toLowerCase()?.replace(/,/g, '')
if (str === 'all') {
return allNum
}
switch (str) {
case 'all':
case 'sugma':
case 'ligma':
case 'pulma':
case 'deez':
case 'max_int':
case 'my soul':
return allNum
case 'sex':
@ -238,6 +243,20 @@ const squadUpgrades = {
effect: cps => cps * 1.2,
cost: 100_000_000_000_000,
emoji: 'printer'
},
discardHumanMorals: {
name: 'Neglect human decency',
description: `Unlocks a new tier of upgrades, but at what cost?`,
effect: cps => cps * 1.1,
cost: 100_000_000_000_000_000,
emoji: 'hole'
},
redemption: {
name: 'Redemption',
description: 'Can you return from the depths of depravity and save your soul?',
effect: cps => cps * 1.1,
cost: 1_000_000_000_000_000_000,
emoji: 'people_hugging'
}
}
@ -249,6 +268,11 @@ const getCompletedSquadgrades = () =>
.filter(squadHas)
.map(([, upgrade]) => upgrade)
const getCompletedSquadgradeNames = () =>
Object.entries(squadUpgrades)
.filter(squadHas)
.map(([name]) => name)
const prestigeMultiplier = user => 1 + ((user.prestige || 0) * 0.01)
const quackGradeMultiplier = user => {
@ -383,5 +407,6 @@ module.exports = {
getRandomFromArray,
chaosFilter,
addReactions,
getCompletedSquadgradeNames,
game
}

View File

@ -69,6 +69,7 @@ const users = {
U0344TFA7HQ: 'Hvacker',
U0X0ZQCN6: 'Caleb',
U03BBTD4CQZ: 'Fernando',
U03DF152WUV: 'Nik',
Sage: 'U028BMEBWBV',
Adam: 'U02U15RFK4Y',
@ -80,6 +81,7 @@ const users = {
Caleb: 'U0X0ZQCN6',
Hvacker: 'U0344TFA7HQ',
Fernando: 'U03BBTD4CQZ',
Nik: 'U03DF152WUV'
}
const activePolls = {}
@ -157,10 +159,12 @@ app.event('message', async ({ event, context, client, say }) => {
const reactCounts = {}
Object.entries(reactPosters).forEach(([id, votes]) => {
votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].includes(v))
console.log(`VOTES FROM ${id}:`, votes)
votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].find(emoji => v.startsWith(emoji)))
if (votes.length === 1) {
reactCounts[votes[0]] ??= 0
reactCounts[votes[0]] += 1
const name = votes[0].replace(/:.*/g, '')
reactCounts[name] ??= 0
reactCounts[name] += 1
}
})