fix(handlers): correct quest tune-value multiplier handling

Two bugs in handleMsgMhfEnumerateQuest affecting reward multipliers:

1. A Value > 0 filter silently dropped any multiplier set to exactly
   0.0 in config, causing the client to fall back to its hardcoded
   default (100%). So ZennyMultiplier: 0.0 produced *full* zenny
   instead of none. Removed the filter so zero values are sent
   verbatim.

2. uint16(float32(0.20) * 100) yields 19, not 20, due to float32
   representation of 0.20 being ~0.19999998. Added a
   multiplierToTuneValue helper using math.Round and applied it to
   all 18 multiplier call sites (HRP/SRP/GRP/GSRP/Zenny/GZenny/
   Material/GMaterial/GCP/GUrgent and NC variants).
This commit is contained in:
Houmgaor
2026-04-06 16:45:56 +02:00
parent 4c725adbb7
commit 49a5069e3d
2 changed files with 28 additions and 26 deletions

View File

@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed quest tune-value filter silently dropping user-configured multipliers set to `0.0`: previously setting e.g. `ZennyMultiplier: 0.0` would strip the entry from the table and fall back to the client's default (100%), producing the opposite of the intended "no zenny" configuration. The `Value > 0` filter in `handleMsgMhfEnumerateQuest` has been removed so zero values are now sent verbatim. Affects HRP/SRP/GRP/GSRP/Zenny/GZenny/Material/GMaterial/GCP/GUrgent multipliers and their NC variants.
- Fixed float32 truncation in quest multiplier conversion: `uint16(0.20 * 100)` yielded `19` instead of `20` because `float32(0.20) ≈ 0.19999998`. Replaced with a `multiplierToTuneValue` helper that rounds via `math.Round`. Applied to all 18 multiplier call sites.
- Fixed `DisableLoginBoost` and `DisableBoostTime` config flags not fully honored ([#187](https://github.com/Mezeporta/Erupe/issues/187)): `GetBoostTimeLimit`/`GetBoostRight` now respect `DisableBoostTime` and `UseKeepLoginBoost` now respects `DisableLoginBoost`. Also fixes a zero-`time.Time` wraparound in `GetBoostTimeLimit` that made the "Boost Time" overlay appear on fresh characters. - Fixed `DisableLoginBoost` and `DisableBoostTime` config flags not fully honored ([#187](https://github.com/Mezeporta/Erupe/issues/187)): `GetBoostTimeLimit`/`GetBoostRight` now respect `DisableBoostTime` and `UseKeepLoginBoost` now respects `DisableLoginBoost`. Also fixes a zero-`time.Time` wraparound in `GetBoostTimeLimit` that made the "Boost Time" overlay appear on fresh characters.
- Fixed playtime regression across sessions: `updateSaveDataWithStruct` now writes the accumulated playtime back into the binary save blob, preventing each reconnect from loading a stale in-game counter and rolling back progress. - Fixed playtime regression across sessions: `updateSaveDataWithStruct` now writes the accumulated playtime back into the binary save blob, preventing each reconnect from loading a stale in-game counter and rolling back progress.
- Fixed player softlock when buying items at the forge: `MSG_CA_EXCHANGE_ITEM` `Parse()` was returning `NOT IMPLEMENTED`, causing the dispatch loop to drop the packet without sending an ACK. Now parses the `AckHandle` and responds with `doAckBufFail` so the client's error branch exits cleanly. - Fixed player softlock when buying items at the forge: `MSG_CA_EXCHANGE_ITEM` `Parse()` was returning `NOT IMPLEMENTED`, causing the dispatch loop to drop the packet without sending an ACK. Now parses the `AckHandle` and responds with `doAckBufFail` so the client's error branch exits cleanly.

View File

@@ -9,6 +9,7 @@ import (
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@@ -503,9 +504,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 1180, Value: 5}, {ID: 1180, Value: 5},
} }
tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)}) tuneValues = append(tuneValues, tuneValue{1020, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GCPMultiplier)})
tuneValues = append(tuneValues, tuneValue{1029, uint16(s.server.erupeConfig.GameplayOptions.GUrgentRate * 100)}) tuneValues = append(tuneValues, tuneValue{1029, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GUrgentRate)})
if s.server.erupeConfig.GameplayOptions.DisableHunterNavi { if s.server.erupeConfig.GameplayOptions.DisableHunterNavi {
tuneValues = append(tuneValues, tuneValue{1037, 1}) tuneValues = append(tuneValues, tuneValue{1037, 1})
@@ -528,29 +529,29 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
} }
// get_hrp_rate_from_rank // get_hrp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3000, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3000, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.HRPMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3338, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3338, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.HRPMultiplierNC))...)
// get_srp_rate_from_rank // get_srp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3013, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3013, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.SRPMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3351, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3351, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.SRPMultiplierNC))...)
// get_grp_rate_from_rank // get_grp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3026, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GRPMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3364, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3364, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GRPMultiplierNC))...)
// get_gsrp_rate_from_rank // get_gsrp_rate_from_rank
tuneValues = append(tuneValues, getTuneValueRange(3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3039, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GSRPMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3377, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3377, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GSRPMultiplierNC))...)
// get_zeny_rate_from_hrank // get_zeny_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3052, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3052, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.ZennyMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3390, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3390, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.ZennyMultiplierNC))...)
// get_zeny_rate_from_grank // get_zeny_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3078, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GZennyMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3416, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3416, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GZennyMultiplierNC))...)
// get_reward_rate_from_hrank // get_reward_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3104, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.MaterialMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3442, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3442, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.MaterialMultiplierNC))...)
// get_reward_rate_from_grank // get_reward_rate_from_grank
tuneValues = append(tuneValues, getTuneValueRange(3130, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplier*100))...) tuneValues = append(tuneValues, getTuneValueRange(3130, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GMaterialMultiplier))...)
tuneValues = append(tuneValues, getTuneValueRange(3468, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplierNC*100))...) tuneValues = append(tuneValues, getTuneValueRange(3468, multiplierToTuneValue(s.server.erupeConfig.GameplayOptions.GMaterialMultiplierNC))...)
// get_lottery_rate_from_hrank // get_lottery_rate_from_hrank
tuneValues = append(tuneValues, getTuneValueRange(3156, 0)...) tuneValues = append(tuneValues, getTuneValueRange(3156, 0)...)
tuneValues = append(tuneValues, getTuneValueRange(3494, 0)...) tuneValues = append(tuneValues, getTuneValueRange(3494, 0)...)
@@ -570,14 +571,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...) tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...)
tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...) tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...)
var temp []tuneValue
for i := range tuneValues {
if tuneValues[i].Value > 0 {
temp = append(temp, tuneValues[i])
}
}
tuneValues = temp
tuneLimit := tuneLimitZZ tuneLimit := tuneLimitZZ
if s.server.erupeConfig.RealClientMode <= cfg.G1 { if s.server.erupeConfig.RealClientMode <= cfg.G1 {
tuneLimit = tuneLimitG1 tuneLimit = tuneLimitG1
@@ -646,6 +639,13 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
// multiplierToTuneValue converts a float32 config multiplier (e.g. 0.20 for 20%)
// into the uint16 percentage value expected by the client tune table. Uses
// rounding to avoid float32 truncation artifacts such as 0.20*100 → 19.
func multiplierToTuneValue(m float32) uint16 {
return uint16(math.Round(float64(m) * 100))
}
func getTuneValueRange(start uint16, value uint16) []tuneValue { func getTuneValueRange(start uint16, value uint16) []tuneValue {
var tv []tuneValue var tv []tuneValue
for i := uint16(0); i < 13; i++ { for i := uint16(0); i < 13; i++ {