合并本地领先源码:旧UI、GM疲劳燃烧、白金徽章孔、宠物修复、支援兵普攻开关

This commit is contained in:
hanfuit committed 2026-11-04 19:20:49 +08:00
1 parent f2debaf9bf
commit 70b3c5a6dc
57 files changed
+4256 -30

No files matched your search

@@ -0,0 +1,434 @@
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
}