Skip to content

Commit

Permalink
Merge branch 'release/3.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
j4rv committed Mar 28, 2024
2 parents 786a4e4 + cfc35f8 commit 5fdff27
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 14 deletions.
3 changes: 2 additions & 1 deletion cmd/jarvbot/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import "time"

var dbFilename = "db.sqlite"

var zero = 0.0
var strongboxMinAmount = 1.0
var strongboxMaxAmount = 64.0
var strongboxMaxAmount = 1000.0
var warnMessageMinLength = 1
var warnMessageMaxLength = 320

Expand Down
101 changes: 101 additions & 0 deletions cmd/jarvbot/genshin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import (
"github.com/j4rv/discord-bot/pkg/genshinchargen"
"github.com/j4rv/discord-bot/pkg/rngx"
artis "github.com/j4rv/genshinartis"
"github.com/j4rv/rollssim"
)

const genshinTeamSize = 4
const genshinChanceIterations = 1000
const averagePullsNote = "**Note:** The average rolls spent on each banner include successful attempts and failed attempts. This includes the best case scenarios of not needing to spend all your pulls to get your desired characters/weapons, and the worst case scenarios of spending all your pulls and not getting your desired characters/weapons."

// Command Answers

Expand Down Expand Up @@ -154,6 +157,104 @@ func answerGenshinDailyCheckInStop(ds *discordgo.Session, mc *discordgo.MessageC

// Slash Command answers

func answerGenshinChance(ds *discordgo.Session, ic *discordgo.InteractionCreate) {
defer func() {
if r := recover(); r != nil {
notifyIfErr("answerGenshinChance panic", fmt.Errorf("%v", r), ds)
}
}()
options := optionMap(ic.ApplicationCommandData().Options)
rollCount := int(options["roll_count"].IntValue())
charCount := int(options["char_count"].IntValue())
weaponCount := int(options["weapon_count"].IntValue())
charPity := optionIntValueOrZero(options["char_pity"])
charGuaranteed := optionBoolValueOrFalse(options["guaranteed_sr_char"])
charRarePity := optionIntValueOrZero(options["char_rare_pity"])
charRareGuaranteed := optionBoolValueOrFalse(options["guaranteed_rare_char"])
weaponPity := optionIntValueOrZero(options["weapon_pity"])
weaponGuaranteed := optionBoolValueOrFalse(options["guaranteed_sr_weapon"])
weaponRarePity := optionIntValueOrZero(options["weapon_rare_pity"])
weaponRareGuaranteed := optionBoolValueOrFalse(options["guaranteed_rare_weapon"])

cumResult := rollssim.WantedRollsResult{}
successCount := 0

for i := 0; i < genshinChanceIterations; i++ {
charBanner := rollssim.GenshinCharRoller{
MihoyoRoller: rollssim.MihoyoRoller{
CurrSRPity: charPity,
GuaranteedRateUpSR: charGuaranteed,
CurrRarePity: charRarePity,
GuaranteedRateUpRare: charRareGuaranteed,
},
}
weaponBanner := rollssim.GenshinWeaponRoller{
MihoyoRoller: rollssim.MihoyoRoller{
CurrSRPity: weaponPity,
GuaranteedRateUpSR: weaponGuaranteed,
CurrRarePity: weaponRarePity,
GuaranteedRateUpRare: weaponRareGuaranteed,
},
FatePoints: 0,
}
result := rollssim.CalcGenshinWantedRolls(rollCount, charCount, weaponCount, &charBanner, &weaponBanner)
cumResult.Add(result)

if result.CharacterBannerRateUpSRCount >= charCount && result.WeaponBannerChosenRateUpCount >= weaponCount {
successCount++
}
}

_, err := textRespond(ds, ic, formatGenshinChanceResult(cumResult, successCount))
notifyIfErr("answerGenshinChance", err, ds)
}

func formatGenshinChanceResult(result rollssim.WantedRollsResult, successCount int) string {
formatted := fmt.Sprintf("## Chance of success: %.2f%%\n",
divideToFloat(successCount, genshinChanceIterations)*100,
)

if result.CharacterBannerRollCount > 0 {
formatted += fmt.Sprintf(`### Character banner:
Average rate up 5\*s characters: %.2f
Average standard 5\*s characters: %.2f
Average rate up 4\*s characters: %.2f
Average standard 4\*s: %.2f
Average rolls spent on character banner: %.2f
`,
divideToFloat(result.CharacterBannerRateUpSRCount, genshinChanceIterations),
divideToFloat(result.CharacterBannerStdSRCount, genshinChanceIterations),
divideToFloat(result.CharacterBannerRateUpRareCount, genshinChanceIterations),
divideToFloat(result.CharacterBannerStdRareCount, genshinChanceIterations),
divideToFloat(result.CharacterBannerRollCount, genshinChanceIterations),
)
}

if result.WeaponBannerRollCount > 0 {
formatted += fmt.Sprintf(`### Weapon banner:
Average chosen rate up weapons: %.2f
Average non-chosen rate up weapons: %.2f
Average standard 5\* weapons: %.2f
Average rate up 4\*s weapons: %.2f
Average standard 4\*s: %.2f
Average rolls spent on weapon banner: %.2f
`,
divideToFloat(result.WeaponBannerChosenRateUpCount, genshinChanceIterations),
divideToFloat(result.WeaponBannerNotChosenRateUpCount, genshinChanceIterations),
divideToFloat(result.WeaponBannerStdSRCount, genshinChanceIterations),
divideToFloat(result.WeaponBannerRateUpRareCount, genshinChanceIterations),
divideToFloat(result.WeaponBannerStdRareCount, genshinChanceIterations),
divideToFloat(result.WeaponBannerRollCount, genshinChanceIterations),
)
}

formatted += averagePullsNote

return formatted
}

func answerStrongbox(ds *discordgo.Session, ic *discordgo.InteractionCreate) {
set := ic.ApplicationCommandData().Options[0].StringValue()
amount := int(ic.ApplicationCommandData().Options[1].IntValue())
Expand Down
2 changes: 2 additions & 0 deletions cmd/jarvbot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func initSlashCommands(ds *discordgo.Session) func() {
ds.AddHandler(func(ds *discordgo.Session, ic *discordgo.InteractionCreate) {
if h, ok := slashHandlers[ic.ApplicationCommandData().Name]; ok {
h(ds, ic)
} else {
log.Println("ERROR couldnt add handler for slash command:", ic.ApplicationCommandData().Name)
}
})

Expand Down
5 changes: 5 additions & 0 deletions cmd/jarvbot/math.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

func divideToFloat(a, b int) float64 {
return float64(a) / float64(b)
}
8 changes: 0 additions & 8 deletions cmd/jarvbot/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"time"

"github.com/bwmarrin/discordgo"
)
Expand Down Expand Up @@ -37,10 +36,3 @@ func isMemberInRole(member *discordgo.Member, roleID string) bool {
}
return false
}

func removeRoleAfterDuration(ds *discordgo.Session, guildID string, memberID string, roleID string, duration time.Duration) {
go func() {
time.Sleep(duration)
ds.GuildMemberRoleRemove(guildID, memberID, roleID)
}()
}
122 changes: 121 additions & 1 deletion cmd/jarvbot/slash_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,92 @@ var slashCommands = []*discordgo.ApplicationCommand{
},
},
},
{
Name: "genshin_chances",
Description: "Calculate your chance to get a Genshin Impact character with specific constellations and refinements",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "roll_count",
Description: "The amount of rolls you have.",
Required: true,
MinValue: &zero,
MaxValue: 1500,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "char_count",
Description: "The amount of characters you want to pull. 0 for none, 1 for C0, 7 for C6.",
Required: true,
MinValue: &zero,
MaxValue: 7,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "weapon_count",
Description: "The amount of weapons you want to pull. 0 for none, 1 for R1, 5 for R5.",
Required: true,
MinValue: &zero,
MaxValue: 5,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "char_pity",
Description: "Your limited character banner 5* pity count.",
Required: false,
MinValue: &zero,
MaxValue: 89,
},
{
Type: discordgo.ApplicationCommandOptionBoolean,
Name: "guaranteed_sr_char",
Description: "If the next 5* character is guaranteed to be the rate up.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "char_rare_pity",
Description: "Your limited character banner 4* pity count.",
Required: false,
MinValue: &zero,
MaxValue: 9,
},
{
Type: discordgo.ApplicationCommandOptionBoolean,
Name: "guaranteed_rare_char",
Description: "If the next 4* character is guaranteed to be a rate up.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "weapon_pity",
Description: "Your limited weapon banner 5* pity count.",
Required: false,
MinValue: &zero,
MaxValue: 89,
},
{
Type: discordgo.ApplicationCommandOptionBoolean,
Name: "guaranteed_sr_weapon",
Description: "If the next 5* weapon is guaranteed to be a rate up.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "weapon_rare_pity",
Description: "Your limited weapon banner 4* pity count.",
Required: false,
MinValue: &zero,
MaxValue: 9,
},
{
Type: discordgo.ApplicationCommandOptionBoolean,
Name: "guaranteed_rare_weapon",
Description: "If the next 4* weapon is guaranteed to be a rate up.",
Required: false,
},
},
},
{
Name: "strongbox",
Description: "Do Strongbox rolls of your set of choice",
Expand Down Expand Up @@ -122,13 +208,47 @@ var slashHandlers = map[string]func(s *discordgo.Session, i *discordgo.Interacti
"help": answerHelp,
"8ball": answer8ball,
"avatar": answerAvatar,
"strongbox": answerStrongbox,
"genshin_chances": expensiveSlashCommand(answerGenshinChance),
"strongbox": expensiveSlashCommand(answerStrongbox),
"character": answerCharacter,
"abyss_challenge": answerAbyssChallenge,
"warn": answerWarn,
"warnings": answerWarnings,
}

func expensiveSlashCommand(expensiveOp func(ds *discordgo.Session, ic *discordgo.InteractionCreate)) func(ds *discordgo.Session, ic *discordgo.InteractionCreate) {
return func(ds *discordgo.Session, ic *discordgo.InteractionCreate) {
if userExpensiveOperationOnCooldown(interactionUser(ic).ID) {
sendDirectMessage(interactionUser(ic).ID, expensiveOperationErrorMsg, ds)
return
}
userExecutedExpensiveOperation(interactionUser(ic).ID)
expensiveOp(ds, ic)
}
}

func optionMap(opts []*discordgo.ApplicationCommandInteractionDataOption) map[string]*discordgo.ApplicationCommandInteractionDataOption {
m := make(map[string]*discordgo.ApplicationCommandInteractionDataOption)
for _, opt := range opts {
m[opt.Name] = opt
}
return m
}

func optionIntValueOrZero(opt *discordgo.ApplicationCommandInteractionDataOption) int {
if opt == nil || opt.Value == nil {
return 0
}
return int(opt.IntValue())
}

func optionBoolValueOrFalse(opt *discordgo.ApplicationCommandInteractionDataOption) bool {
if opt == nil || opt.Value == nil {
return false
}
return opt.BoolValue()
}

func textRespond(ds *discordgo.Session, ic *discordgo.InteractionCreate, textResponse string) (*discordgo.InteractionResponse, error) {
response := &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Expand Down
27 changes: 27 additions & 0 deletions cmd/jarvbot/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,40 @@ import (
"strconv"
"strings"
"time"

"github.com/bwmarrin/discordgo"
)

var stringHoursRegex = regexp.MustCompile(`^(\d{1,2})h`)
var stringMinsRegex = regexp.MustCompile(`^(\d{1,2})m`)
var stringSecsRegex = regexp.MustCompile(`^(\d{1,2})s`)

const secondsInADay = 60 * 60 * 24
const expensiveOperationCooldown = 5 * 60 * time.Second
const expensiveOperationErrorMsg = "You just executed an expensive operation, please wait."

func removeRoleAfterDuration(ds *discordgo.Session, guildID string, memberID string, roleID string, duration time.Duration) {
go func() {
time.Sleep(duration)
ds.GuildMemberRoleRemove(guildID, memberID, roleID)
}()
}

var usersOnExpensiveOperationCooldown = make(map[string]struct{})

func userExpensiveOperationOnCooldown(userID string) bool {
_, inCooldown := usersOnExpensiveOperationCooldown[userID]
return inCooldown
}

func userExecutedExpensiveOperation(userID string) {
usersOnExpensiveOperationCooldown[userID] = struct{}{}

go func() {
time.Sleep(expensiveOperationCooldown)
delete(usersOnExpensiveOperationCooldown, userID)
}()
}

// Format: "!<command> 99h 99m 99s <body>"
// Returns: The duration and the body
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ require (

require (
github.com/gorilla/websocket v1.5.1 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
github.com/j4rv/rollssim v0.0.0-20240328173749-ed06730e3472 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/j4rv/genshinartis v0.0.0-20230911183910-b19c3dc323f3 h1:bdBq6XxKRQvZWRyunxC/Svo5cTEfJ49S75qBvOUSH9w=
github.com/j4rv/genshinartis v0.0.0-20230911183910-b19c3dc323f3/go.mod h1:NXUI4xa2rw8D2BjTBqgT6934wIJXJgn1vG9x/FGXW4E=
github.com/j4rv/rollssim v0.0.0-20240328145921-b2c451d22b1d h1:lAPUtWKh8GSUIlD0W0pvihcuTey9+oThRyYiamTALjU=
github.com/j4rv/rollssim v0.0.0-20240328145921-b2c451d22b1d/go.mod h1:giZ+GfPdq1sGjpWZHM5YSQ7Pfz2/doB9I2uSUXl/ZxU=
github.com/j4rv/rollssim v0.0.0-20240328153522-13b27c74ce9e h1:+0hVvtd5zchRJM0io+PHwbm3cQtTb1kkQPYOKYJZZF0=
github.com/j4rv/rollssim v0.0.0-20240328153522-13b27c74ce9e/go.mod h1:giZ+GfPdq1sGjpWZHM5YSQ7Pfz2/doB9I2uSUXl/ZxU=
github.com/j4rv/rollssim v0.0.0-20240328172521-0660e174d545 h1:HYsFGFpYIRotH/vrUfKdORN0RPSctSCHM2aCuOQRcmg=
github.com/j4rv/rollssim v0.0.0-20240328172521-0660e174d545/go.mod h1:giZ+GfPdq1sGjpWZHM5YSQ7Pfz2/doB9I2uSUXl/ZxU=
github.com/j4rv/rollssim v0.0.0-20240328173749-ed06730e3472 h1:z6LRJfZUXlw5R9koVUlsAb9GoFp2NLC1ihyjfPNacso=
github.com/j4rv/rollssim v0.0.0-20240328173749-ed06730e3472/go.mod h1:giZ+GfPdq1sGjpWZHM5YSQ7Pfz2/doB9I2uSUXl/ZxU=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
Expand All @@ -21,12 +29,18 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Loading

0 comments on commit 5fdff27

Please sign in to comment.