服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
141 lines
5.8 KiB
Go
141 lines
5.8 KiB
Go
package dnfbridge
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
dnfavatarfitting "longheng.io/server/internal/modules/dnf/avatarfitting"
|
|
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
|
)
|
|
|
|
type currentAvatarFittingRoomResult struct {
|
|
Domain dnfavatarfitting.Result
|
|
Changed [currentAvatarFittingRoomPartCount]bool
|
|
}
|
|
|
|
func (s *Service) commitCurrentAvatarFittingRoom(
|
|
ctx context.Context,
|
|
session *gameSession,
|
|
catalog *pvfDungeonDropCatalog,
|
|
request currentAvatarFittingRoomRequest,
|
|
) (currentAvatarFittingRoomResult, error) {
|
|
if s == nil || session == nil || session.selectedCharacterID == 0 || catalog == nil || catalog.source == nil {
|
|
return currentAvatarFittingRoomResult{}, errCurrentAvatarFittingRoomUnavailable
|
|
}
|
|
repositories, ok := s.repositoryGroup()
|
|
if !ok || repositories.Character == nil || repositories.CharacterSettlement == nil {
|
|
return currentAvatarFittingRoomResult{}, errCurrentAvatarFittingRoomUnavailable
|
|
}
|
|
accountID := strings.TrimSpace(s.accountIDForSession(session))
|
|
characterID := strconv.FormatUint(uint64(session.selectedCharacterID), 10)
|
|
if accountID == "" {
|
|
return currentAvatarFittingRoomResult{}, errCurrentAvatarFittingRoomUnavailable
|
|
}
|
|
character, found, err := repositories.Character.Load(ctx, characterID)
|
|
if err != nil || !found || strings.TrimSpace(character.AccountID) != accountID {
|
|
return currentAvatarFittingRoomResult{}, errors.Join(errCurrentAvatarFittingRoomUnavailable, err)
|
|
}
|
|
job := numericCharacterStat(character.Job)
|
|
if job < 0 || job > math.MaxUint8 {
|
|
return currentAvatarFittingRoomResult{}, errCurrentAvatarFittingRoomUnavailable
|
|
}
|
|
config, err := loadCurrentAvatarFittingRoomConfig(catalog.source)
|
|
if err != nil {
|
|
return currentAvatarFittingRoomResult{}, err
|
|
}
|
|
selectedSet, err := currentAvatarFittingRoomSetForJob(config, byte(job))
|
|
if err != nil {
|
|
return currentAvatarFittingRoomResult{}, err
|
|
}
|
|
outputs := selectedSet.Jobs[byte(job)]
|
|
command := dnfavatarfitting.Command{
|
|
AccountID: accountID,
|
|
CharacterID: characterID,
|
|
InDungeon: currentCrystalContractDungeonOwnedBySession(session),
|
|
TicketItemID: int64(config.TicketItemID),
|
|
GaugeThreshold: config.GaugeThreshold,
|
|
}
|
|
var result currentAvatarFittingRoomResult
|
|
for index, record := range request.Records {
|
|
if record.ObjectIndex == -1 && record.CurrentItemID == -1 {
|
|
command.Changes[index] = dnfavatarfitting.Change{
|
|
EquipmentSlot: -1,
|
|
ObjectIndex: -1,
|
|
CurrentItemID: -1,
|
|
}
|
|
continue
|
|
}
|
|
if record.ObjectIndex < 0 || record.CurrentItemID <= 0 || outputs[index] == 0 {
|
|
return currentAvatarFittingRoomResult{}, fmt.Errorf("%w: part=%d", errCurrentAvatarFittingRoomRequestInvalid, index)
|
|
}
|
|
currentDefinition, currentDocument, resolveErr := currentAvatarCompoundDefinition(catalog, uint32(record.CurrentItemID))
|
|
if resolveErr != nil {
|
|
return currentAvatarFittingRoomResult{}, errors.Join(errCurrentAvatarFittingRoomEquipment, resolveErr)
|
|
}
|
|
currentPart, partOK := currentAvatarCompoundDocumentPart(currentDocument)
|
|
if !partOK || currentPart != currentAvatarFittingRoomPartNames[index] {
|
|
return currentAvatarFittingRoomResult{}, fmt.Errorf("%w: part=%d current=%s", errCurrentAvatarFittingRoomEquipment, index, currentPart)
|
|
}
|
|
outputDefinition, outputDocument, resolveErr := currentAvatarCompoundDefinition(catalog, outputs[index])
|
|
if resolveErr != nil {
|
|
return currentAvatarFittingRoomResult{}, errors.Join(errCurrentAvatarFittingRoomEquipment, resolveErr)
|
|
}
|
|
outputPart, partOK := currentAvatarCompoundDocumentPart(outputDocument)
|
|
if !partOK || outputPart != currentAvatarFittingRoomPartNames[index] {
|
|
return currentAvatarFittingRoomResult{}, fmt.Errorf("%w: part=%d output=%s item=%d", errCurrentAvatarFittingRoomEquipment, index, outputPart, outputs[index])
|
|
}
|
|
command.Changes[index] = dnfavatarfitting.Change{
|
|
EquipmentSlot: currentAvatarFittingRoomEquipmentSlots[index],
|
|
ObjectIndex: record.ObjectIndex,
|
|
CurrentItemID: int64(record.CurrentItemID),
|
|
OutputItemID: int64(outputs[index]),
|
|
PVFPath: outputDefinition.PVFPath,
|
|
EquipmentType: outputDefinition.EquipmentType,
|
|
}
|
|
result.Changed[index] = true
|
|
_ = currentDefinition
|
|
}
|
|
|
|
if config.GaugeThreshold > 0 && config.GaugeRewardID > 0 && config.GaugeRewardCount > 0 {
|
|
rewardDefinition, resolveErr := catalog.ResolveItem(config.GaugeRewardID)
|
|
if resolveErr != nil {
|
|
return currentAvatarFittingRoomResult{}, errors.Join(errCurrentAvatarFittingRoomUnavailable, resolveErr)
|
|
}
|
|
rewardDefinition, resolveErr = currentPVFItemDefinitionForGrantAt(rewardDefinition, time.Now().UTC())
|
|
if resolveErr != nil || rewardDefinition.Kind != dungeonDropItemStackable || rewardDefinition.SlotStart < 0 || rewardDefinition.SlotEnd < rewardDefinition.SlotStart {
|
|
return currentAvatarFittingRoomResult{}, errors.Join(errCurrentAvatarFittingRoomUnavailable, resolveErr)
|
|
}
|
|
stackLimit := rewardDefinition.StackLimit
|
|
if stackLimit <= 0 {
|
|
stackLimit = math.MaxUint32
|
|
}
|
|
command.Reward = &dnfavatarfitting.Reward{
|
|
ItemID: int64(config.GaugeRewardID),
|
|
CountPerCrossing: config.GaugeRewardCount,
|
|
SlotStart: rewardDefinition.SlotStart,
|
|
SlotEnd: rewardDefinition.SlotEnd,
|
|
StackLimit: stackLimit,
|
|
BuildStack: func(slot int16, count int64) (dnfrepo.ItemStack, error) {
|
|
if count <= 0 || count > math.MaxUint32 {
|
|
return dnfrepo.ItemStack{}, errCurrentAvatarFittingRoomUnavailable
|
|
}
|
|
return buildCurrentDungeonPickupStack(slot, rewardDefinition, uint32(count))
|
|
},
|
|
}
|
|
}
|
|
owner, err := dnfavatarfitting.NewOwner(repositories)
|
|
if err != nil {
|
|
return currentAvatarFittingRoomResult{}, errors.Join(errCurrentAvatarFittingRoomUnavailable, err)
|
|
}
|
|
result.Domain, err = owner.Commit(ctx, command)
|
|
if err != nil {
|
|
return currentAvatarFittingRoomResult{}, err
|
|
}
|
|
return result, nil
|
|
}
|