diff --git a/src/boost/artifacts.go b/src/boost/artifacts.go index f4e828ba..0f23d7f3 100644 --- a/src/boost/artifacts.go +++ b/src/boost/artifacts.go @@ -321,6 +321,9 @@ func populateArtifactsFromBackup(client dc.Client, userID string) (string, strin if b := contract.Boosters[userID]; b != nil { b.ArtifactSet = getUserArtifacts(userID, nil) rate, logStr := CalculateIHRRateFromDB(userID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } b.IHRRate = rate b.IHRCalcLog = logStr updatedContracts++ diff --git a/src/boost/boost.go b/src/boost/boost.go index 16df96ef..a198e995 100644 --- a/src/boost/boost.go +++ b/src/boost/boost.go @@ -415,6 +415,9 @@ func AddBoostTokens(client dc.Client, e dc.InteractionEvent, setCountWant int, c if contract.BoostOrder == ContractOrderIHR || contract.BoostOrder == ContractOrderIHRFuzzy { for uID, booster := range contract.Boosters { rate, logStr := CalculateIHRRateFromDB(uID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } booster.IHRRate = rate booster.IHRCalcLog = logStr } @@ -747,6 +750,7 @@ func AddFarmerToContract(client dc.Client, contract *Contract, guildID string, c b.Register = time.Now() b.UserID = userID b.Color = 0x00cc00 + b.IHRRate = DefaultLeggyIHR var user, err = client.User(userID) if err != nil { @@ -1265,6 +1269,9 @@ func updateContractFarmerTE(client dc.Client, userID string, b *Booster, contrac b.IHRCalcLog = fmt.Sprintf("IHR Calculation (Manual for %s): Final=%0.2f", userID, manualIHR) } else { rate, logStr := CalculateIHRRateFromBackup(backup, userID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } b.IHRRate = rate b.IHRCalcLog = logStr } @@ -1313,6 +1320,9 @@ func updateContractFarmerTE(client dc.Client, userID string, b *Booster, contrac b.IHRCalcLog = fmt.Sprintf("IHR Calculation (Manual for %s): Final=%0.2f", userID, manualIHR) } else { rate, logStr := CalculateIHRRateFromDB(userID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } b.IHRRate = rate b.IHRCalcLog = logStr } diff --git a/src/boost/boost_draw.go b/src/boost/boost_draw.go index 699a713d..5d99849c 100644 --- a/src/boost/boost_draw.go +++ b/src/boost/boost_draw.go @@ -386,7 +386,7 @@ func DrawBoostList(contract *Contract) []dc.LayoutComponent { sortRate = " **IHR:0** " } } else { - sortRate = fmt.Sprintf(" **IHR:%s** ", ei.FormatEIValue(b.IHRRate, map[string]any{"decimals": 2, "trim": true})) + sortRate = fmt.Sprintf(" **IHR:%0.2fx** ", b.IHRRate/DefaultLeggyIHR) } } if (contract.State == ContractStateBanker || contract.State == ContractStateFastrun) && contract.PlayStyle != ContractPlaystyleChill { diff --git a/src/boost/boost_draw_test.go b/src/boost/boost_draw_test.go index 543d5b12..4b52fb18 100644 --- a/src/boost/boost_draw_test.go +++ b/src/boost/boost_draw_test.go @@ -154,3 +154,48 @@ func TestDrawBoostListSkipsUnusableBanner(t *testing.T) { t.Error("DrawBoostList dropped the media gallery for a usable banner URL") } } + +func TestDrawBoostListIHRMultiple(t *testing.T) { + contract := &Contract{ + ContractHash: "ihr-test-hash", + ContractID: "ihr-test-contract", + CoopID: "ihr-test-coop", + State: ContractStateSignup, + BoostOrder: ContractOrderIHR, + CreatorID: []string{"creator-id"}, + Order: []string{"u1", "u2"}, + Boosters: map[string]*Booster{ + "u1": { + UserID: "u1", + Mention: "<@u1>", + Name: "Player1", + TokensWanted: 6, + IHRRate: 33480.0, // 4.5x 7440.0 + }, + "u2": { + UserID: "u2", + Mention: "<@u2>", + Name: "Player2", + TokensWanted: 6, + IHRRate: 7440.0, // 1.0x 7440.0 + }, + }, + Location: []*LocationData{{GuildID: "guild1", ChannelID: "channel1"}}, + } + + components := DrawBoostList(contract) + var outputBuilder strings.Builder + for _, comp := range components { + if textDisplay, ok := comp.(dc.TextDisplay); ok { + outputBuilder.WriteString(textDisplay.Content) + } + } + output := outputBuilder.String() + + if !strings.Contains(output, "**IHR:4.50x**") { + t.Errorf("expected output to contain **IHR:4.50x**, got %q", output) + } + if !strings.Contains(output, "**IHR:1.00x**") { + t.Errorf("expected output to contain **IHR:1.00x**, got %q", output) + } +} diff --git a/src/boost/contract.go b/src/boost/contract.go index 086b2f40..3c92f52b 100644 --- a/src/boost/contract.go +++ b/src/boost/contract.go @@ -829,10 +829,13 @@ func HandleContractSettingsReactions(client dc.Client, e *dc.ComponentEvent) { for userID, b := range contract.Boosters { // Recalculate IHR rate from DB for all boosters so pre-change values excluding deflector stones are updated rate, logStr := CalculateIHRRateFromDB(userID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } b.IHRRate = rate b.IHRCalcLog = logStr - if b.IHRRate == 0 { + if b.IHRRate <= DefaultLeggyIHR { usersToRefresh = append(usersToRefresh, userToRefresh{userID: userID, booster: b}) } } diff --git a/src/boost/contract_test.go b/src/boost/contract_test.go index 505b90d9..5dbe8d3b 100644 --- a/src/boost/contract_test.go +++ b/src/boost/contract_test.go @@ -1094,3 +1094,36 @@ func TestUpdateThreadName_SkipIfUnchanged(t *testing.T) { t.Errorf("expected EditChannel with name %q, got: %v", expectedName, edits) } } + +func TestAddFarmerToContract_MinimumIHR(t *testing.T) { + client := dctest.New(). + WithGuild("guild1", "Guild 1"). + WithChannel("channel1", "guild1", "contract-channel"). + WithUser("user-without-ihr", "farmer_no_ihr", "Farmer No IHR") + + contract := &Contract{ + ContractHash: "test-hash-ihr-min", + ContractID: "test-contract", + CoopID: "test-coop", + CoopSize: 10, + State: ContractStateSignup, + BoostOrder: ContractOrderSignup, + CreatorID: []string{"creator1"}, + Order: make([]string, 0), + Boosters: make(map[string]*Booster), + Location: []*LocationData{{GuildID: "guild1", ChannelID: "channel1"}}, + } + Contracts[contract.ContractHash] = contract + defer delete(Contracts, contract.ContractHash) + + b, err := AddFarmerToContract(client, contract, "guild1", "channel1", "user-without-ihr", ContractOrderSignup, false, false) + if err != nil { + t.Fatalf("unexpected error adding farmer: %v", err) + } + if b == nil { + t.Fatalf("expected booster to be created, got nil") + } + if b.IHRRate < DefaultLeggyIHR { + t.Errorf("expected b.IHRRate >= %f, got %f", DefaultLeggyIHR, b.IHRRate) + } +} diff --git a/src/boost/update_farmer.go b/src/boost/update_farmer.go index 87097a8c..b5627061 100644 --- a/src/boost/update_farmer.go +++ b/src/boost/update_farmer.go @@ -248,14 +248,20 @@ func updateFarmerInContracts(client dc.Client, userID string, subcommand string, case "te": booster.TECount = int(value) rate, logStr := CalculateIHRRateFromDB(userID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } booster.IHRRate = rate booster.IHRCalcLog = logStr case "ihr": - booster.IHRRate = float64(value) - booster.IHRCalcLog = fmt.Sprintf("IHR Calculation (Manual for %s): Final=%0.2f", userID, float64(value)) + booster.IHRRate = max(DefaultLeggyIHR, float64(value)) + booster.IHRCalcLog = fmt.Sprintf("IHR Calculation (Manual for %s): Final=%0.2f", userID, booster.IHRRate) case "artifacts": booster.ArtifactSet = getUserArtifacts(userID, nil) rate, logStr := CalculateIHRRateFromDB(userID) + if rate < DefaultLeggyIHR { + rate = DefaultLeggyIHR + } booster.IHRRate = rate booster.IHRCalcLog = logStr }