435 lines
15 KiB
Go
435 lines
15 KiB
Go
package dnfbridge
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
dnfauction "longheng.io/server/internal/modules/dnf/auction"
|
|
)
|
|
|
|
type currentServerFeatureSettings struct {
|
|
RaidReviveSeconds int `json:"raidReviveSeconds"`
|
|
AllDeadReturnSeconds int `json:"allDeadReturnSeconds"`
|
|
AllowFreeRevive *bool `json:"allowFreeRevive"`
|
|
HellOrbChance int `json:"hellOrbChance"`
|
|
HellDropChanceDenominator int `json:"hellDropChanceDenominator"`
|
|
EquipmentRollMultiplier int `json:"equipmentRollMultiplier"`
|
|
EpicPieceRollMultiplier int `json:"epicPieceRollMultiplier"`
|
|
AntonRaidAttackSeconds int `json:"antonRaidAttackSeconds"`
|
|
LukeEnergyRecoveryPercent int `json:"lukeEnergyRecoveryPercent"`
|
|
FiendWarSquadCount int `json:"fiendWarSquadCount"`
|
|
RoomFatigueCost int `json:"roomFatigueCost"`
|
|
DailyFatigueResetHour int `json:"dailyFatigueResetHour"`
|
|
AdventureGroupBonusPercent int `json:"adventureGroupBonusPercent"`
|
|
RentalMaxReinforce int `json:"rentalMaxReinforce"`
|
|
RentalMaxForge int `json:"rentalMaxForge"`
|
|
ReviveByDungeon map[string]int `json:"reviveByDungeon"`
|
|
AntonBlackFogRecoverySeconds int `json:"antonBlackFogRecoverySeconds"`
|
|
AntonBlackFogActiveSeconds int `json:"antonBlackFogActiveSeconds"`
|
|
AntonQuakeRecoverySeconds int `json:"antonQuakeRecoverySeconds"`
|
|
AntonQuakeActiveSeconds int `json:"antonQuakeActiveSeconds"`
|
|
AntonNavalOpenDelaySeconds int `json:"antonNavalOpenDelaySeconds"`
|
|
AntonNavalRecoverySeconds int `json:"antonNavalRecoverySeconds"`
|
|
AntonNavalActiveSeconds int `json:"antonNavalActiveSeconds"`
|
|
LukeEnergyRecovery0Seconds int `json:"lukeEnergyRecovery0Seconds"`
|
|
LukeEnergyRecovery1Seconds int `json:"lukeEnergyRecovery1Seconds"`
|
|
LukeEnergyRecovery2Seconds int `json:"lukeEnergyRecovery2Seconds"`
|
|
LukePhaseTimeoutPercent int `json:"lukePhaseTimeoutPercent"`
|
|
FiendWarAreaLimitPercent int `json:"fiendWarAreaLimitPercent"`
|
|
FiendWarDieImprisonPercent int `json:"fiendWarDieImprisonPercent"`
|
|
SoloTeleportCooldownSeconds int `json:"soloTeleportCooldownSeconds"`
|
|
SoloTeleportCooldownIndices string `json:"soloTeleportCooldownIndices"`
|
|
AuctionStandardListingLimit int `json:"auctionStandardListingLimit"`
|
|
AuctionPremiumListingLimit int `json:"auctionPremiumListingLimit"`
|
|
AuctionStandardDurationHours int `json:"auctionStandardDurationHours"`
|
|
AuctionPremiumDurationHours int `json:"auctionPremiumDurationHours"`
|
|
AntonMemberMax int `json:"antonMemberMax"`
|
|
AntonStartMinimumMembers int `json:"antonStartMinimumMembers"`
|
|
AntonSingleStartMinimumMembers int `json:"antonSingleStartMinimumMembers"`
|
|
AntonStartDelaySeconds int `json:"antonStartDelaySeconds"`
|
|
AntonPhaseBreakSeconds int `json:"antonPhaseBreakSeconds"`
|
|
AntonEntryCostItem int `json:"antonEntryCostItem"`
|
|
AntonEntryCostCount int `json:"antonEntryCostCount"`
|
|
AntonWeeklyEnterCountA int `json:"antonWeeklyEnterCountA"`
|
|
AntonWeeklyEnterCountB int `json:"antonWeeklyEnterCountB"`
|
|
AntonPhaseTimeoutSeconds string `json:"antonPhaseTimeoutSeconds"`
|
|
LukeDailyAccountEnterLimit int `json:"lukeDailyAccountEnterLimit"`
|
|
LukeSanctumTimerCount int `json:"lukeSanctumTimerCount"`
|
|
LukeEntryCostItem int `json:"lukeEntryCostItem"`
|
|
LukeEntryCostCount int `json:"lukeEntryCostCount"`
|
|
FiendWarMemberMax int `json:"fiendWarMemberMax"`
|
|
FiendWarStartMinimumMembers int `json:"fiendWarStartMinimumMembers"`
|
|
FiendWarDisconnectWaitSeconds int `json:"fiendWarDisconnectWaitSeconds"`
|
|
FiendWarEntryCostItem int `json:"fiendWarEntryCostItem"`
|
|
FiendWarEntryCostCount int `json:"fiendWarEntryCostCount"`
|
|
FiendWarRetry *bool `json:"fiendWarRetry"`
|
|
FiendWarDailyEnterLimit int `json:"fiendWarDailyEnterLimit"`
|
|
AuctionStandardFeePercent int `json:"auctionStandardFeePercent"`
|
|
AuctionPremiumFeePercent int `json:"auctionPremiumFeePercent"`
|
|
}
|
|
|
|
func GMSaveServerFeatures(data []byte) error {
|
|
var values map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &values); err != nil {
|
|
return err
|
|
}
|
|
path := currentServerConfigPath("server-features.json")
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func currentServerConfigPath(name string) string {
|
|
candidates := []string{filepath.Join("config", name)}
|
|
if executable, err := os.Executable(); err == nil {
|
|
candidates = append(candidates,
|
|
filepath.Join(filepath.Dir(executable), "..", "config", name),
|
|
filepath.Join(filepath.Dir(executable), "config", name),
|
|
)
|
|
}
|
|
for _, candidate := range candidates {
|
|
if _, err := os.Stat(candidate); err == nil {
|
|
return candidate
|
|
}
|
|
}
|
|
return candidates[0]
|
|
}
|
|
|
|
func currentServerFeatures() currentServerFeatureSettings {
|
|
data, err := os.ReadFile(currentServerConfigPath("server-features.json"))
|
|
if err != nil {
|
|
return currentServerFeatureSettings{}
|
|
}
|
|
var settings currentServerFeatureSettings
|
|
if err := json.Unmarshal(data, &settings); err != nil {
|
|
return currentServerFeatureSettings{}
|
|
}
|
|
return settings
|
|
}
|
|
|
|
func currentActivityEnabled(key string) bool {
|
|
data, err := os.ReadFile(currentServerConfigPath("activity.json"))
|
|
if err != nil {
|
|
return true
|
|
}
|
|
var values map[string]bool
|
|
if err := json.Unmarshal(data, &values); err != nil {
|
|
return true
|
|
}
|
|
enabled, found := values[key]
|
|
if !found {
|
|
return true
|
|
}
|
|
return enabled
|
|
}
|
|
|
|
func currentConfiguredAllDeadReturnDelay() time.Duration {
|
|
const fallback = currentPartyDungeonAllDeadReturnDelay
|
|
settings := currentServerFeatures()
|
|
if settings.AllDeadReturnSeconds < 1 || settings.AllDeadReturnSeconds > 60 {
|
|
return fallback
|
|
}
|
|
return time.Duration(settings.AllDeadReturnSeconds) * time.Second
|
|
}
|
|
|
|
func currentConfiguredAllowFreeRevive() bool {
|
|
settings := currentServerFeatures()
|
|
if settings.AllowFreeRevive == nil {
|
|
return currentDungeonUseCoinAllowFreeRevive
|
|
}
|
|
return *settings.AllowFreeRevive
|
|
}
|
|
|
|
func currentConfiguredHellOrbChance() int {
|
|
settings := currentServerFeatures()
|
|
if settings.HellOrbChance < 0 || settings.HellOrbChance > currentDungeonHellOrbRollDenominator {
|
|
return currentDungeonHellOrbChance
|
|
}
|
|
return settings.HellOrbChance
|
|
}
|
|
|
|
func currentConfiguredHellDropChanceDenominator() int {
|
|
settings := currentServerFeatures()
|
|
if settings.HellDropChanceDenominator < 1 || settings.HellDropChanceDenominator > 1_000_000 {
|
|
return currentHellPartyDropChanceDenominator
|
|
}
|
|
return settings.HellDropChanceDenominator
|
|
}
|
|
|
|
func currentConfiguredAntonRaidAttackSeconds() int {
|
|
settings := currentServerFeatures()
|
|
if settings.AntonRaidAttackSeconds < 60 || settings.AntonRaidAttackSeconds > 24*60*60 {
|
|
return antonRaidAttackSeconds
|
|
}
|
|
return settings.AntonRaidAttackSeconds
|
|
}
|
|
|
|
func currentConfiguredLukeEnergyRecoveryPercent() int {
|
|
settings := currentServerFeatures()
|
|
if settings.LukeEnergyRecoveryPercent < 1 || settings.LukeEnergyRecoveryPercent > 1000 {
|
|
return 100
|
|
}
|
|
return settings.LukeEnergyRecoveryPercent
|
|
}
|
|
|
|
func currentConfiguredFiendWarSquadCount() int {
|
|
settings := currentServerFeatures()
|
|
if settings.FiendWarSquadCount < 1 || settings.FiendWarSquadCount > 8 {
|
|
return currentFiendWarSquadCount
|
|
}
|
|
return settings.FiendWarSquadCount
|
|
}
|
|
|
|
func currentConfiguredRoomFatigueCost() int64 {
|
|
settings := currentServerFeatures()
|
|
if settings.RoomFatigueCost < 0 || settings.RoomFatigueCost > 100 {
|
|
return currentDungeonRoomFatigueCost
|
|
}
|
|
return int64(settings.RoomFatigueCost)
|
|
}
|
|
|
|
func currentConfiguredDailyFatigueResetHour() int {
|
|
settings := currentServerFeatures()
|
|
if settings.DailyFatigueResetHour < 0 || settings.DailyFatigueResetHour > 23 {
|
|
return 6
|
|
}
|
|
return settings.DailyFatigueResetHour
|
|
}
|
|
|
|
func currentConfiguredAdventureGroupBonusPercent() int {
|
|
settings := currentServerFeatures()
|
|
if settings.AdventureGroupBonusPercent < 0 || settings.AdventureGroupBonusPercent > 1000 {
|
|
return currentDungeonAdventureGroupDefaultClearBonusPercent
|
|
}
|
|
return settings.AdventureGroupBonusPercent
|
|
}
|
|
|
|
func currentConfiguredRentalMaxReinforce() int {
|
|
settings := currentServerFeatures()
|
|
if settings.RentalMaxReinforce < 0 || settings.RentalMaxReinforce > 31 {
|
|
return currentRentalMaxReinforce
|
|
}
|
|
return settings.RentalMaxReinforce
|
|
}
|
|
|
|
func currentConfiguredRentalMaxForge() int {
|
|
settings := currentServerFeatures()
|
|
if settings.RentalMaxForge < 0 || settings.RentalMaxForge > 255 {
|
|
return currentRentalMaxForge
|
|
}
|
|
return settings.RentalMaxForge
|
|
}
|
|
|
|
func currentConfiguredDungeonReviveSeconds(dungeonID int64) (int, bool) {
|
|
if dungeonID <= 0 {
|
|
return 0, false
|
|
}
|
|
settings := currentServerFeatures()
|
|
if settings.ReviveByDungeon == nil {
|
|
return 0, false
|
|
}
|
|
value, found := settings.ReviveByDungeon[strconv.FormatInt(dungeonID, 10)]
|
|
if !found || value < 1 || value > 86400 {
|
|
return 0, false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
func currentConfiguredDungeonReviveDelay(dungeonID int64) time.Duration {
|
|
seconds, found := currentConfiguredDungeonReviveSeconds(dungeonID)
|
|
if !found {
|
|
return currentConfiguredRaidReviveDelay()
|
|
}
|
|
return time.Duration(seconds) * time.Second
|
|
}
|
|
|
|
func currentAntonDuration(configured int, fallback time.Duration) time.Duration {
|
|
if configured < 1 || configured > 86400 {
|
|
return fallback
|
|
}
|
|
return time.Duration(configured) * time.Second
|
|
}
|
|
|
|
func currentAntonBlackFogRecovery() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonBlackFogRecoverySeconds, antonBlackFogRecovery)
|
|
}
|
|
|
|
func currentAntonBlackFogActive() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonBlackFogActiveSeconds, antonBlackFogActive)
|
|
}
|
|
|
|
func currentAntonQuakeRecovery() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonQuakeRecoverySeconds, antonQuakeRecovery)
|
|
}
|
|
|
|
func currentAntonQuakeActive() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonQuakeActiveSeconds, antonQuakeActive)
|
|
}
|
|
|
|
func currentAntonNavalOpenDelay() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonNavalOpenDelaySeconds, antonNavalOpenDelay)
|
|
}
|
|
|
|
func currentAntonNavalRecovery() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonNavalRecoverySeconds, antonNavalRecovery)
|
|
}
|
|
|
|
func currentAntonNavalActive() time.Duration {
|
|
return currentAntonDuration(currentServerFeatures().AntonNavalActiveSeconds, antonNavalActive)
|
|
}
|
|
|
|
func currentLukeEnergyRecovery(tier int) time.Duration {
|
|
if tier < 0 {
|
|
tier = 0
|
|
}
|
|
if tier >= len(lukeEnergyControlRecovery) {
|
|
tier = len(lukeEnergyControlRecovery) - 1
|
|
}
|
|
settings := currentServerFeatures()
|
|
values := [...]int{
|
|
settings.LukeEnergyRecovery0Seconds,
|
|
settings.LukeEnergyRecovery1Seconds,
|
|
settings.LukeEnergyRecovery2Seconds,
|
|
}
|
|
if values[tier] >= 1 && values[tier] <= 86400 {
|
|
return time.Duration(values[tier]) * time.Second
|
|
}
|
|
percent := settings.LukeEnergyRecoveryPercent
|
|
if percent < 1 || percent > 1000 {
|
|
percent = 100
|
|
}
|
|
return time.Duration(int64(lukeEnergyControlRecovery[tier]) * int64(percent) / 100)
|
|
}
|
|
|
|
func currentLukePhaseTimeoutPercent() int {
|
|
percent := currentServerFeatures().LukePhaseTimeoutPercent
|
|
if percent < 1 || percent > 1000 {
|
|
return 100
|
|
}
|
|
return percent
|
|
}
|
|
|
|
func currentFiendWarAreaLimitPercent() int {
|
|
percent := currentServerFeatures().FiendWarAreaLimitPercent
|
|
if percent < 1 || percent > 1000 {
|
|
return 100
|
|
}
|
|
return percent
|
|
}
|
|
|
|
func currentFiendWarDieImprisonPercent() int {
|
|
percent := currentServerFeatures().FiendWarDieImprisonPercent
|
|
if percent < 1 || percent > 1000 {
|
|
return 100
|
|
}
|
|
return percent
|
|
}
|
|
|
|
func currentConfiguredSoloTeleportCooldownSeconds() uint32 {
|
|
seconds := currentServerFeatures().SoloTeleportCooldownSeconds
|
|
if seconds < 1 || seconds > 86400 {
|
|
return currentSoloTeleportCooldownSeconds
|
|
}
|
|
return uint32(seconds)
|
|
}
|
|
|
|
func currentConfiguredSoloTeleportIndices() []byte {
|
|
raw := strings.TrimSpace(currentServerFeatures().SoloTeleportCooldownIndices)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
if strings.EqualFold(raw, "off") || strings.EqualFold(raw, "none") {
|
|
return []byte{}
|
|
}
|
|
var indices []byte
|
|
for _, field := range strings.Split(raw, ",") {
|
|
value, err := strconv.ParseUint(strings.TrimSpace(field), 10, 8)
|
|
if err == nil && currentSoloTeleportCooldownIndexAccepted(byte(value)) {
|
|
indices = append(indices, byte(value))
|
|
}
|
|
}
|
|
return indices
|
|
}
|
|
|
|
func currentConfiguredAuctionListingLimit(premium bool) int {
|
|
settings := currentServerFeatures()
|
|
value := settings.AuctionStandardListingLimit
|
|
if premium {
|
|
value = settings.AuctionPremiumListingLimit
|
|
}
|
|
if value < 1 || value > 1000 {
|
|
if premium {
|
|
return currentAuctionPremiumListingLimit
|
|
}
|
|
return currentAuctionStandardListingLimit
|
|
}
|
|
return value
|
|
}
|
|
|
|
func currentConfiguredAuctionDuration(premium bool) time.Duration {
|
|
settings := currentServerFeatures()
|
|
hours := settings.AuctionStandardDurationHours
|
|
if premium {
|
|
hours = settings.AuctionPremiumDurationHours
|
|
}
|
|
if hours < 1 || hours > 720 {
|
|
if premium {
|
|
return currentAuctionPremiumDuration
|
|
}
|
|
return currentAuctionStandardDuration
|
|
}
|
|
return time.Duration(hours) * time.Hour
|
|
}
|
|
|
|
func currentConfiguredAuctionFeePercent(premium bool) int64 {
|
|
settings := currentServerFeatures()
|
|
value := settings.AuctionStandardFeePercent
|
|
if premium {
|
|
value = settings.AuctionPremiumFeePercent
|
|
}
|
|
if value <= 0 || value > 100 {
|
|
return dnfauction.SaleFeePercentFor(premium)
|
|
}
|
|
return int64(value)
|
|
}
|
|
|
|
// currentConfiguredRaidReviveDelay reads the optional runtime/config/
|
|
// server-features.json written by the standalone server tool. It only changes
|
|
// the server-side death watchdog; the current EXE still owns its ten-second
|
|
// revive prompt.
|
|
func currentConfiguredRaidReviveDelay() time.Duration {
|
|
const fallback = currentDungeonDeathReturnDelay
|
|
settings := currentServerFeatures()
|
|
if settings.RaidReviveSeconds < 1 || settings.RaidReviveSeconds > 60 {
|
|
return fallback
|
|
}
|
|
return time.Duration(settings.RaidReviveSeconds) * time.Second
|
|
}
|
|
|
|
// currentConfiguredEquipmentRollMultiplier keeps the local test-end special
|
|
// drop tuning (2x equipment rolls) configurable by the standalone server tool.
|
|
func currentConfiguredEquipmentRollMultiplier() int {
|
|
settings := currentServerFeatures()
|
|
if settings.EquipmentRollMultiplier < 1 || settings.EquipmentRollMultiplier > 100 {
|
|
return 1
|
|
}
|
|
return settings.EquipmentRollMultiplier
|
|
}
|
|
|
|
// currentConfiguredEpicPieceRollMultiplier keeps the local test-end special
|
|
// drop tuning (3x epic fragment rolls) configurable by the standalone tool.
|
|
func currentConfiguredEpicPieceRollMultiplier() int {
|
|
settings := currentServerFeatures()
|
|
if settings.EpicPieceRollMultiplier < 1 || settings.EpicPieceRollMultiplier > 100 {
|
|
return 1
|
|
}
|
|
return settings.EpicPieceRollMultiplier
|
|
}
|
|
|