You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
463 lines
20 KiB
Go
463 lines
20 KiB
Go
package tuning
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// Values contains runtime-tunable gameplay knobs loaded from DB.
|
|
// Missing JSON fields keep default values.
|
|
type Values struct {
|
|
EncounterCooldownBaseMs int64 `json:"encounterCooldownBaseMs"`
|
|
EncounterActivityBase float64 `json:"encounterActivityBase"`
|
|
|
|
BaseMoveSpeed float64 `json:"baseMoveSpeed"`
|
|
MovementTickRateMs int64 `json:"movementTickRateMs"`
|
|
PositionSyncRateMs int64 `json:"positionSyncRateMs"`
|
|
|
|
TownRestMinMs int64 `json:"townRestMinMs"`
|
|
TownRestMaxMs int64 `json:"townRestMaxMs"`
|
|
TownRestHPPerS float64 `json:"townRestHpPerSecond"`
|
|
TownArrivalRadius float64 `json:"townArrivalRadius"`
|
|
TownNPCVisitChance float64 `json:"townNpcVisitChance"`
|
|
// TownNPCApproachChance: second roll after a visit timer fires — whether the hero commits to walking
|
|
// toward the next queued NPC. 1.0 = same as legacy (only TownNPCVisitChance gates approach).
|
|
TownNPCApproachChance float64 `json:"townNpcApproachChance"`
|
|
// TownNPCInteractChance: offline only — after reaching an NPC, probability of “using” services
|
|
// (buy potion, full heal, accept a quest) instead of walking past.
|
|
TownNPCInteractChance float64 `json:"townNpcInteractChance"`
|
|
TownNPCRollMinMs int64 `json:"townNpcRollMinMs"`
|
|
TownNPCRollMaxMs int64 `json:"townNpcRollMaxMs"`
|
|
TownNPCRetryMs int64 `json:"townNpcRetryMs"`
|
|
TownNPCPauseMs int64 `json:"townNpcPauseMs"`
|
|
TownNPCLogIntervalMs int64 `json:"townNpcLogIntervalMs"`
|
|
TownNPCWalkSpeed float64 `json:"townNpcWalkSpeed"`
|
|
// TownNPCStandoffWorld: hero stops this many world units short of the NPC tile (along approach).
|
|
TownNPCStandoffWorld float64 `json:"townNpcStandoffWorld"`
|
|
// TownAfterNPCRestChance: after the NPC tour, at town center — probability of a full town rest
|
|
// (same duration/regen as towns without NPCs). Otherwise only a short TownNPCPauseMs wait.
|
|
TownAfterNPCRestChance float64 `json:"townAfterNpcRestChance"`
|
|
|
|
WanderingMerchantPromptTimeoutMs int64 `json:"wanderingMerchantPromptTimeoutMs"`
|
|
MerchantCostBase int64 `json:"merchantCostBase"`
|
|
MerchantCostPerLevel int64 `json:"merchantCostPerLevel"`
|
|
MerchantTownAutoSellShare float64 `json:"merchantTownAutoSellShare"`
|
|
MonsterEncounterWeightBase float64 `json:"monsterEncounterWeightBase"`
|
|
MonsterEncounterWeightWildBonus float64 `json:"monsterEncounterWeightWildBonus"`
|
|
MerchantEncounterWeightBase float64 `json:"merchantEncounterWeightBase"`
|
|
MerchantEncounterWeightRoadBonus float64 `json:"merchantEncounterWeightRoadBonus"`
|
|
|
|
LootChanceCommon float64 `json:"lootChanceCommon"`
|
|
LootChanceUncommon float64 `json:"lootChanceUncommon"`
|
|
LootChanceRare float64 `json:"lootChanceRare"`
|
|
LootChanceEpic float64 `json:"lootChanceEpic"`
|
|
LootChanceLegendary float64 `json:"lootChanceLegendary"`
|
|
GoldLootScale float64 `json:"goldLootScale"`
|
|
PotionDropChance float64 `json:"potionDropChance"`
|
|
EquipmentDropBase float64 `json:"equipmentDropBase"`
|
|
|
|
GoldCommonMin int64 `json:"goldCommonMin"`
|
|
GoldCommonMax int64 `json:"goldCommonMax"`
|
|
GoldUncommonMin int64 `json:"goldUncommonMin"`
|
|
GoldUncommonMax int64 `json:"goldUncommonMax"`
|
|
GoldRareMin int64 `json:"goldRareMin"`
|
|
GoldRareMax int64 `json:"goldRareMax"`
|
|
GoldEpicMin int64 `json:"goldEpicMin"`
|
|
GoldEpicMax int64 `json:"goldEpicMax"`
|
|
GoldLegendaryMin int64 `json:"goldLegendaryMin"`
|
|
GoldLegendaryMax int64 `json:"goldLegendaryMax"`
|
|
|
|
AutoSellCommon int64 `json:"autoSellCommon"`
|
|
AutoSellUncommon int64 `json:"autoSellUncommon"`
|
|
AutoSellRare int64 `json:"autoSellRare"`
|
|
AutoSellEpic int64 `json:"autoSellEpic"`
|
|
AutoSellLegendary int64 `json:"autoSellLegendary"`
|
|
|
|
RESTEncounterCooldownMs int64 `json:"restEncounterCooldownMs"`
|
|
RESTEncounterNPCChance float64 `json:"restEncounterNpcChance"`
|
|
NPCCostHeal int64 `json:"npcCostHeal"`
|
|
NPCCostPotion int64 `json:"npcCostPotion"`
|
|
NPCCostNearbyRadius float64 `json:"npcCostNearbyRadius"`
|
|
// QuestOffersPerNPC caps how many quest templates a quest_giver offers per interaction (after filtering taken quests).
|
|
QuestOffersPerNPC int `json:"questOffersPerNPC"`
|
|
// QuestOfferRefreshHours controls how often quest_giver offers rotate (hours).
|
|
QuestOfferRefreshHours int `json:"questOfferRefreshHours"`
|
|
|
|
CombatDamageScale float64 `json:"combatDamageScale"`
|
|
CombatDamageRollMin float64 `json:"combatDamageRollMin"`
|
|
CombatDamageRollMax float64 `json:"combatDamageRollMax"`
|
|
// EnemyCombatDamageScale / Roll* apply only when an enemy hits the hero (not hero→enemy).
|
|
EnemyCombatDamageScale float64 `json:"enemyCombatDamageScale"`
|
|
EnemyCombatDamageRollMin float64 `json:"enemyCombatDamageRollMin"`
|
|
EnemyCombatDamageRollMax float64 `json:"enemyCombatDamageRollMax"`
|
|
EnemyDodgeChance float64 `json:"enemyDodgeChance"`
|
|
EnemyCriticalMinChance float64 `json:"enemyCriticalMinChance"`
|
|
EnemyCritChanceCap float64 `json:"enemyCritChanceCap"`
|
|
HeroCritChanceCap float64 `json:"heroCritChanceCap"`
|
|
HeroBlockChancePerDefense float64 `json:"heroBlockChancePerDefense"`
|
|
HeroBlockChanceCap float64 `json:"heroBlockChanceCap"`
|
|
EnemyBurstEveryN int64 `json:"enemyBurstEveryN"`
|
|
EnemyBurstMultiplier float64 `json:"enemyBurstMultiplier"`
|
|
EnemyChainEveryN int64 `json:"enemyChainEveryN"`
|
|
EnemyChainMultiplier float64 `json:"enemyChainMultiplier"`
|
|
|
|
DebuffProcBurn float64 `json:"debuffProcBurn"`
|
|
DebuffProcPoison float64 `json:"debuffProcPoison"`
|
|
DebuffProcSlow float64 `json:"debuffProcSlow"`
|
|
DebuffProcStun float64 `json:"debuffProcStun"`
|
|
DebuffProcFreeze float64 `json:"debuffProcFreeze"`
|
|
DebuffProcIceSlow float64 `json:"debuffProcIceSlow"`
|
|
|
|
EnemyRegenDefault float64 `json:"enemyRegenDefault"`
|
|
EnemyRegenSkeletonKing float64 `json:"enemyRegenSkeletonKing"`
|
|
EnemyRegenForestWarden float64 `json:"enemyRegenForestWarden"`
|
|
EnemyRegenBattleLizard float64 `json:"enemyRegenBattleLizard"`
|
|
SummonCycleSeconds int64 `json:"summonCycleSeconds"`
|
|
SummonDamageDivisor int64 `json:"summonDamageDivisor"`
|
|
LuckBuffMultiplier float64 `json:"luckBuffMultiplier"`
|
|
|
|
MinAttackIntervalMs int64 `json:"minAttackIntervalMs"`
|
|
CombatPaceMultiplier int64 `json:"combatPaceMultiplier"`
|
|
|
|
PotionHealPercent float64 `json:"potionHealPercent"`
|
|
PotionAutoUseThreshold float64 `json:"potionAutoUseThreshold"`
|
|
ReviveHpPercent float64 `json:"reviveHpPercent"`
|
|
AutoReviveAfterMs int64 `json:"autoReviveAfterMs"`
|
|
|
|
XPCurveEarlyBase float64 `json:"xpCurveEarlyBase"`
|
|
XPCurveEarlyScale float64 `json:"xpCurveEarlyScale"`
|
|
XPCurveMidBase float64 `json:"xpCurveMidBase"`
|
|
XPCurveMidScale float64 `json:"xpCurveMidScale"`
|
|
XPCurveLateBase float64 `json:"xpCurveLateBase"`
|
|
XPCurveLateScale float64 `json:"xpCurveLateScale"`
|
|
|
|
LevelUpHPEvery int64 `json:"levelUpHpEvery"`
|
|
LevelUpATKEvery int64 `json:"levelUpAtkEvery"`
|
|
LevelUpDEFEvery int64 `json:"levelUpDefEvery"`
|
|
LevelUpSTREvery int64 `json:"levelUpStrEvery"`
|
|
LevelUpCONEvery int64 `json:"levelUpConEvery"`
|
|
LevelUpAGIEvery int64 `json:"levelUpAgiEvery"`
|
|
LevelUpLUCKEvery int64 `json:"levelUpLuckEvery"`
|
|
|
|
AgilityCoef float64 `json:"agilityCoef"`
|
|
MaxAttackSpeed float64 `json:"maxAttackSpeed"`
|
|
MinAttackSpeed float64 `json:"minAttackSpeed"`
|
|
|
|
IlvlFactorSlope float64 `json:"ilvlFactorSlope"`
|
|
RarityMultiplierCommon float64 `json:"rarityMultiplierCommon"`
|
|
RarityMultiplierUncommon float64 `json:"rarityMultiplierUncommon"`
|
|
RarityMultiplierRare float64 `json:"rarityMultiplierRare"`
|
|
RarityMultiplierEpic float64 `json:"rarityMultiplierEpic"`
|
|
RarityMultiplierLegendary float64 `json:"rarityMultiplierLegendary"`
|
|
RollIlvlEliteBaseChance float64 `json:"rollIlvlEliteBaseChance"`
|
|
RollIlvlElitePlusOneChance float64 `json:"rollIlvlElitePlusOneChance"`
|
|
|
|
BuffChargePeriodMs int64 `json:"buffChargePeriodMs"`
|
|
FreeBuffActivationsPerPeriod int64 `json:"freeBuffActivationsPerPeriod"`
|
|
SubscriptionDurationMs int64 `json:"subscriptionDurationMs"`
|
|
SubscriptionWeeklyPriceRUB int64 `json:"subscriptionWeeklyPriceRub"`
|
|
BuffRefillPriceRUB int64 `json:"buffRefillPriceRub"`
|
|
ResurrectionRefillPriceRUB int64 `json:"resurrectionRefillPriceRub"`
|
|
MaxRevivesFree int64 `json:"maxRevivesFree"`
|
|
MaxRevivesSubscriber int64 `json:"maxRevivesSubscriber"`
|
|
|
|
EnemyScaleBandHP float64 `json:"enemyScaleBandHp"`
|
|
EnemyScaleOvercapHP float64 `json:"enemyScaleOvercapHp"`
|
|
EnemyScaleBandATK float64 `json:"enemyScaleBandAtk"`
|
|
EnemyScaleOvercapATK float64 `json:"enemyScaleOvercapAtk"`
|
|
EnemyScaleBandDEF float64 `json:"enemyScaleBandDef"`
|
|
EnemyScaleOvercapDEF float64 `json:"enemyScaleOvercapDef"`
|
|
EnemyScaleBandXP float64 `json:"enemyScaleBandXp"`
|
|
EnemyScaleOvercapXP float64 `json:"enemyScaleOvercapXp"`
|
|
EnemyScaleBandGold float64 `json:"enemyScaleBandGold"`
|
|
EnemyScaleOvercapGold float64 `json:"enemyScaleOvercapGold"`
|
|
|
|
AutoEquipThreshold float64 `json:"autoEquipThreshold"`
|
|
LootHistoryLimit int64 `json:"lootHistoryLimit"`
|
|
|
|
// --- Adventure / excursion (mini-adventure off-road) ---
|
|
|
|
// AdventureStartChance is the per-tick probability of starting an adventure while walking.
|
|
// With 500ms ticks and ~50% walking uptime, 0.0001 ≈ 3 adventures per 8 h.
|
|
AdventureStartChance float64 `json:"adventureStartChance"`
|
|
// AdventureCooldownMs is the minimum wall-time between two adventure sessions.
|
|
AdventureCooldownMs int64 `json:"adventureCooldownMs"`
|
|
// AdventureOutDurationMs is how long the "out" phase lasts (hero moves off-road into forest).
|
|
AdventureOutDurationMs int64 `json:"adventureOutDurationMs"`
|
|
// AdventureWildMinMs / AdventureWildMaxMs define the random range for the "wild" phase
|
|
// (encounters in the forest). Total adventure ≈ out + wild + return.
|
|
AdventureWildMinMs int64 `json:"adventureWildMinMs"`
|
|
AdventureWildMaxMs int64 `json:"adventureWildMaxMs"`
|
|
// AdventureReturnDurationMs is how long the "return" phase lasts (hero walks back to road).
|
|
AdventureReturnDurationMs int64 `json:"adventureReturnDurationMs"`
|
|
// AdventureDepthWorldUnits is the max perpendicular offset from road during an adventure.
|
|
AdventureDepthWorldUnits float64 `json:"adventureDepthWorldUnits"`
|
|
// AdventureEncounterCooldownMs is the encounter cooldown while in the wild/return phases.
|
|
AdventureEncounterCooldownMs int64 `json:"adventureEncounterCooldownMs"`
|
|
// AdventureReturnEncounterEnabled allows encounters during the return phase.
|
|
AdventureReturnEncounterEnabled bool `json:"adventureReturnEncounterEnabled"`
|
|
// AdventureReturnWildnessMin is the minimum wilderness factor (0..1) used during return.
|
|
AdventureReturnWildnessMin float64 `json:"adventureReturnWildnessMin"`
|
|
|
|
// --- HP-based rest triggers ---
|
|
|
|
// LowHpThreshold is the HP/MaxHP fraction below which rest may trigger (0..1).
|
|
LowHpThreshold float64 `json:"lowHpThreshold"`
|
|
// RoadsideRestExitHp is the HP/MaxHP fraction at which roadside rest ends early (0..1).
|
|
RoadsideRestExitHp float64 `json:"roadsideRestExitHp"`
|
|
// AdventureRestTargetHp is the HP/MaxHP fraction at which adventure inline rest ends (0..1).
|
|
AdventureRestTargetHp float64 `json:"adventureRestTargetHp"`
|
|
// RoadsideRestMinMs is the minimum duration for a roadside rest period.
|
|
RoadsideRestMinMs int64 `json:"roadsideRestMinMs"`
|
|
// RoadsideRestMaxMs is the maximum duration for a roadside rest period.
|
|
RoadsideRestMaxMs int64 `json:"roadsideRestMaxMs"`
|
|
// RoadsideRestHpPerS is the HP/MaxHP fraction healed per second during roadside rest.
|
|
RoadsideRestHpPerS float64 `json:"roadsideRestHpPerSecond"`
|
|
// AdventureRestHpPerS is the HP/MaxHP fraction healed per second during adventure inline rest.
|
|
AdventureRestHpPerS float64 `json:"adventureRestHpPerSecond"`
|
|
|
|
// RoadsideRestDepthWorldUnits is the perpendicular offset from road during roadside rest.
|
|
RoadsideRestDepthWorldUnits float64 `json:"roadsideRestDepthWorldUnits"`
|
|
}
|
|
|
|
func DefaultValues() Values {
|
|
return Values{
|
|
EncounterCooldownBaseMs: 12_000,
|
|
EncounterActivityBase: 0.035,
|
|
BaseMoveSpeed: 2.0,
|
|
MovementTickRateMs: 500,
|
|
PositionSyncRateMs: 10_000,
|
|
TownRestMinMs: 5 * 60 * 1000,
|
|
TownRestMaxMs: 20 * 60 * 1000,
|
|
TownRestHPPerS: 0.002,
|
|
TownArrivalRadius: 0.5,
|
|
TownNPCVisitChance: 0.78,
|
|
TownNPCApproachChance: 1.0,
|
|
TownNPCInteractChance: 0.65,
|
|
TownNPCRollMinMs: 800,
|
|
TownNPCRollMaxMs: 2600,
|
|
TownNPCRetryMs: 450,
|
|
TownNPCPauseMs: 30_000,
|
|
TownNPCLogIntervalMs: 5_000,
|
|
TownNPCWalkSpeed: 3.0,
|
|
TownNPCStandoffWorld: 0.65,
|
|
TownAfterNPCRestChance: 0.78,
|
|
WanderingMerchantPromptTimeoutMs: 15_000,
|
|
MerchantCostBase: 20,
|
|
MerchantCostPerLevel: 5,
|
|
MerchantTownAutoSellShare: 0.30,
|
|
MonsterEncounterWeightBase: 0.62,
|
|
MonsterEncounterWeightWildBonus: 0.18,
|
|
MerchantEncounterWeightBase: 0.04,
|
|
MerchantEncounterWeightRoadBonus: 0.10,
|
|
LootChanceCommon: 0.40,
|
|
LootChanceUncommon: 0.10,
|
|
LootChanceRare: 0.02,
|
|
LootChanceEpic: 0.003,
|
|
LootChanceLegendary: 0.0005,
|
|
GoldLootScale: 0.5,
|
|
PotionDropChance: 0.05,
|
|
EquipmentDropBase: 0.15,
|
|
GoldCommonMin: 0,
|
|
GoldCommonMax: 5,
|
|
GoldUncommonMin: 6,
|
|
GoldUncommonMax: 20,
|
|
GoldRareMin: 21,
|
|
GoldRareMax: 50,
|
|
GoldEpicMin: 51,
|
|
GoldEpicMax: 120,
|
|
GoldLegendaryMin: 121,
|
|
GoldLegendaryMax: 300,
|
|
AutoSellCommon: 3,
|
|
AutoSellUncommon: 8,
|
|
AutoSellRare: 20,
|
|
AutoSellEpic: 60,
|
|
AutoSellLegendary: 180,
|
|
RESTEncounterCooldownMs: 16_000,
|
|
RESTEncounterNPCChance: 0.10,
|
|
NPCCostHeal: 100,
|
|
NPCCostPotion: 50,
|
|
NPCCostNearbyRadius: 3.0,
|
|
QuestOffersPerNPC: 2,
|
|
QuestOfferRefreshHours: 2,
|
|
CombatDamageScale: 0.35,
|
|
CombatDamageRollMin: 0.60,
|
|
CombatDamageRollMax: 1.10,
|
|
EnemyCombatDamageScale: DefaultEnemyCombatDamageScale,
|
|
EnemyCombatDamageRollMin: DefaultEnemyCombatDamageRollMin,
|
|
EnemyCombatDamageRollMax: DefaultEnemyCombatDamageRollMax,
|
|
EnemyDodgeChance: 0.20,
|
|
EnemyCriticalMinChance: 0.10,
|
|
EnemyCritChanceCap: 0.20,
|
|
HeroCritChanceCap: 0.12,
|
|
HeroBlockChancePerDefense: 0.0025,
|
|
HeroBlockChanceCap: 0.20,
|
|
EnemyBurstEveryN: 3,
|
|
EnemyBurstMultiplier: 1.5,
|
|
EnemyChainEveryN: 6,
|
|
EnemyChainMultiplier: 3.0,
|
|
DebuffProcBurn: 0.30,
|
|
DebuffProcPoison: 0.10,
|
|
DebuffProcSlow: 0.25,
|
|
DebuffProcStun: 0.25,
|
|
DebuffProcFreeze: 0.20,
|
|
DebuffProcIceSlow: 0.20,
|
|
EnemyRegenDefault: 0.02,
|
|
EnemyRegenSkeletonKing: 0.10,
|
|
EnemyRegenForestWarden: 0.05,
|
|
EnemyRegenBattleLizard: 0.01,
|
|
SummonCycleSeconds: 15,
|
|
SummonDamageDivisor: 4,
|
|
LuckBuffMultiplier: 1.75,
|
|
MinAttackIntervalMs: 250,
|
|
CombatPaceMultiplier: 5,
|
|
PotionHealPercent: 0.30,
|
|
PotionAutoUseThreshold: 0.30,
|
|
ReviveHpPercent: 0.50,
|
|
AutoReviveAfterMs: int64(time.Hour / time.Millisecond),
|
|
XPCurveEarlyBase: 180,
|
|
XPCurveEarlyScale: 1.28,
|
|
XPCurveMidBase: 1450,
|
|
XPCurveMidScale: 1.15,
|
|
XPCurveLateBase: 23000,
|
|
XPCurveLateScale: 1.10,
|
|
LevelUpHPEvery: 10,
|
|
LevelUpATKEvery: 30,
|
|
LevelUpDEFEvery: 30,
|
|
LevelUpSTREvery: 40,
|
|
LevelUpCONEvery: 50,
|
|
LevelUpAGIEvery: 60,
|
|
LevelUpLUCKEvery: 100,
|
|
AgilityCoef: 0.03,
|
|
MaxAttackSpeed: 4.0,
|
|
MinAttackSpeed: 0.1,
|
|
IlvlFactorSlope: 0.03,
|
|
RarityMultiplierCommon: 1.00,
|
|
RarityMultiplierUncommon: 1.12,
|
|
RarityMultiplierRare: 1.30,
|
|
RarityMultiplierEpic: 1.52,
|
|
RarityMultiplierLegendary: 1.78,
|
|
RollIlvlEliteBaseChance: 0.4,
|
|
RollIlvlElitePlusOneChance: 0.4,
|
|
BuffChargePeriodMs: 24 * 60 * 60 * 1000,
|
|
FreeBuffActivationsPerPeriod: 2,
|
|
SubscriptionDurationMs: 7 * 24 * 60 * 60 * 1000,
|
|
SubscriptionWeeklyPriceRUB: 299,
|
|
BuffRefillPriceRUB: 50,
|
|
ResurrectionRefillPriceRUB: 150,
|
|
MaxRevivesFree: 1,
|
|
MaxRevivesSubscriber: 2,
|
|
EnemyScaleBandHP: 0.05,
|
|
EnemyScaleOvercapHP: 0.025,
|
|
EnemyScaleBandATK: 0.035,
|
|
EnemyScaleOvercapATK: 0.018,
|
|
EnemyScaleBandDEF: 0.035,
|
|
EnemyScaleOvercapDEF: 0.018,
|
|
EnemyScaleBandXP: 0.05,
|
|
EnemyScaleOvercapXP: 0.03,
|
|
EnemyScaleBandGold: 0.05,
|
|
EnemyScaleOvercapGold: 0.025,
|
|
AutoEquipThreshold: 1.03,
|
|
LootHistoryLimit: 50,
|
|
|
|
AdventureStartChance: 0.0001,
|
|
AdventureCooldownMs: 300_000,
|
|
AdventureOutDurationMs: 20_000,
|
|
AdventureWildMinMs: 560_000,
|
|
AdventureWildMaxMs: 2_960_000,
|
|
AdventureReturnDurationMs: 20_000,
|
|
AdventureDepthWorldUnits: 40.0,
|
|
AdventureEncounterCooldownMs: 6_000,
|
|
AdventureReturnEncounterEnabled: true,
|
|
AdventureReturnWildnessMin: 0.35,
|
|
|
|
LowHpThreshold: 0.25,
|
|
RoadsideRestExitHp: 0.70,
|
|
AdventureRestTargetHp: 0.70,
|
|
RoadsideRestMinMs: 240_000,
|
|
RoadsideRestMaxMs: 600_000,
|
|
RoadsideRestHpPerS: 0.003,
|
|
AdventureRestHpPerS: 0.004,
|
|
|
|
RoadsideRestDepthWorldUnits: 12.0,
|
|
}
|
|
}
|
|
|
|
var current atomic.Value
|
|
|
|
func init() {
|
|
v := DefaultValues()
|
|
current.Store(&v)
|
|
}
|
|
|
|
func Get() Values {
|
|
p := current.Load().(*Values)
|
|
return *p
|
|
}
|
|
|
|
// EffectiveNPCShopCosts returns potion and full-heal prices from runtime tuning (DB-merged JSON),
|
|
// falling back to defaults when unset or non-positive.
|
|
func EffectiveNPCShopCosts() (potionCost, healCost int64) {
|
|
cfg := Get()
|
|
potionCost = cfg.NPCCostPotion
|
|
if potionCost <= 0 {
|
|
potionCost = DefaultValues().NPCCostPotion
|
|
}
|
|
healCost = cfg.NPCCostHeal
|
|
if healCost <= 0 {
|
|
healCost = DefaultValues().NPCCostHeal
|
|
}
|
|
return potionCost, healCost
|
|
}
|
|
|
|
// EffectiveQuestOffersPerNPC returns the max quest offers per quest_giver interaction from runtime tuning.
|
|
func EffectiveQuestOffersPerNPC() int {
|
|
n := Get().QuestOffersPerNPC
|
|
if n <= 0 {
|
|
return DefaultValues().QuestOffersPerNPC
|
|
}
|
|
return n
|
|
}
|
|
|
|
// EffectiveQuestOfferRefreshHours returns the rotation cadence (hours) for quest_giver offers.
|
|
func EffectiveQuestOfferRefreshHours() int {
|
|
n := Get().QuestOfferRefreshHours
|
|
if n <= 0 {
|
|
return DefaultValues().QuestOfferRefreshHours
|
|
}
|
|
return n
|
|
}
|
|
|
|
func Set(v Values) {
|
|
current.Store(&v)
|
|
}
|
|
|
|
type PayloadLoader interface {
|
|
LoadRuntimeConfigPayload(ctx context.Context) ([]byte, error)
|
|
}
|
|
|
|
func ReloadNow(ctx context.Context, logger *slog.Logger, loader PayloadLoader) error {
|
|
payload, err := loader.LoadRuntimeConfigPayload(ctx)
|
|
if err != nil {
|
|
if logger != nil {
|
|
logger.Warn("runtime config reload failed", "error", err)
|
|
}
|
|
return err
|
|
}
|
|
next := DefaultValues()
|
|
if len(payload) > 0 {
|
|
if err := json.Unmarshal(payload, &next); err != nil {
|
|
if logger != nil {
|
|
logger.Warn("runtime config payload parse failed", "error", err)
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
Set(next)
|
|
return nil
|
|
}
|
|
|