DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

@@ -0,0 +1,736 @@
package dnfbridge
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"math"
"strconv"
"strings"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
// currentStackIsSingleEquipment 报告这一行是不是「一件装备」。
//
// 装备一件一行:PVF 对所有 equipment 都按单件处理。线上存在 Extra.item_kind=equipment
// 且 Count=2..49 的历史堆叠行(魔盒发奖缺陷,现场 2026-09-27 线上 90 行 357 件),
// 而客户端装备行的 0x77 里 0x06 是品质种子而不是数量,玩家界面上看不见 N。凡是
// 「一份材料作用一件」的入口(打孔、镶嵌、跨界寄送)都必须先过这一关;非装备行放行。
func currentStackIsSingleEquipment(stack dnfrepo.ItemStack) bool {
kind := strings.ToLower(strings.TrimSpace(stack.Extra["item_kind"]))
if kind == "" {
kind = strings.ToLower(strings.TrimSpace(stack.Extra["kind"]))
}
if kind != "equipment" {
return true
}
return stack.Count == 1
}
func currentFindMainInventorySocketTarget(inventory dnfrepo.InventoryRecord, requestedSlot int16, itemID int64) (string, int16, dnfrepo.ItemStack, error) {
if itemID <= 0 {
return "", 0, dnfrepo.ItemStack{}, fmt.Errorf("%w: item=%d", errCurrentSocketTargetMissing, itemID)
}
if requestedSlot >= 0 {
key := currentSocketInventoryKey(currentSocketListMain, requestedSlot)
if stack, ok := inventory.Slots[key]; ok && stack.ItemID == itemID && stack.ItemID > 0 {
if !currentStackIsSingleEquipment(stack) {
return "", 0, dnfrepo.ItemStack{}, fmt.Errorf("%w: item=%d count=%d", errCurrentSocketTargetMissing, itemID, stack.Count)
}
return key, requestedSlot, stack, nil
}
}
var (
foundKey string
foundSlot int16
foundStack dnfrepo.ItemStack
matches int
)
for key, stack := range inventory.Slots {
listType, slot, ok := parseSceneInventorySlotKey(key)
if !ok || listType != currentSocketListMain || stack.ItemID != itemID || stack.ItemID <= 0 {
continue
}
foundKey = key
foundSlot = slot
foundStack = stack
matches++
}
switch matches {
case 0:
return "", 0, dnfrepo.ItemStack{}, fmt.Errorf("%w: client_key=%d item=%d", errCurrentSocketTargetMissing, requestedSlot, itemID)
case 1:
if !currentStackIsSingleEquipment(foundStack) {
return "", 0, dnfrepo.ItemStack{}, fmt.Errorf("%w: item=%d count=%d", errCurrentSocketTargetMissing, itemID, foundStack.Count)
}
return foundKey, foundSlot, foundStack, nil
default:
return "", 0, dnfrepo.ItemStack{}, fmt.Errorf("%w: client_key=%d item=%d matches=%d", errCurrentSocketTargetAmbiguous, requestedSlot, itemID, matches)
}
}
func currentOpenEquipmentSocketWithMaterial(catalog *pvfDungeonDropCatalog, inventory *dnfrepo.InventoryRecord, materialSlot int16, rule currentEquipmentPlacementRule) (int, error) {
if inventory == nil {
return 0, errCurrentSocketInventoryMissing
}
material, ok := inventory.Slots[currentSocketInventoryKey(currentSocketListMain, materialSlot)]
if !ok || material.ItemID <= 0 || material.Count <= 0 {
return 0, fmt.Errorf("%w: list=%d slot=%d", errCurrentSocketMaterialMissing, currentSocketListMain, materialSlot)
}
if err := currentEquipmentSocketOpenMaterialRule(catalog, material.ItemID); err != nil {
return 0, err
}
if _, _, err := consumeCurrentSocketStack(inventory, currentSocketListMain, materialSlot, material.ItemID, true); err != nil {
return 0, err
}
return currentEquipmentSocketOpenCount(rule), nil
}
func currentEquipmentSocketOpenCount(rule currentEquipmentPlacementRule) int {
if rule.targetSlot == 22 || rule.targetSlot == 23 {
return 1
}
return 2
}
func currentEquipmentJewelSocketType(rule currentEquipmentPlacementRule) byte {
switch rule.targetSlot {
case 14, 16:
return 0x04
case 15, 19:
return 0x02
case 18, 21:
return 0x01
case 17, 20:
return 0x08
default:
return 0x10
}
}
func currentCanAttachEmblem(socketType, emblemType byte) bool {
return socketType == 0 || emblemType == 0 || (socketType&emblemType) != 0
}
func currentApplyEquipmentEmblems(catalog *pvfDungeonDropCatalog, rule currentEquipmentPlacementRule, inventory *dnfrepo.InventoryRecord, data *[currentEquipmentEmblemDataBytes]byte, emblems []currentEmblemApplyRequest) error {
if inventory == nil || data == nil {
return errCurrentSocketInventoryMissing
}
openCount := int(data[0])
maxOpen := currentEquipmentSocketOpenCount(rule)
if openCount <= 0 {
return errCurrentSocketNoOpenSockets
}
currentEnsureEquipmentEmblemSocketsOpen(data, maxOpen, openCount)
socketType := currentEquipmentJewelSocketType(rule)
for _, emblem := range emblems {
logicalIndex, ok := currentResolveEquipmentSocketRequest(rule, openCount, emblem.SocketIndex)
if !ok {
return fmt.Errorf("%w: socket=%d open=%d", errCurrentSocketIndexInvalid, emblem.SocketIndex, openCount)
}
emblemType := currentSocketEmblemType(catalog, emblem.EmblemItemID)
if !currentCanAttachEmblem(socketType, emblemType) {
return fmt.Errorf("%w: socket_type=0x%02x emblem_type=0x%02x emblem=%d", errCurrentSocketTypeMismatch, socketType, emblemType, emblem.EmblemItemID)
}
if _, _, err := consumeCurrentSocketStack(inventory, currentSocketListMain, emblem.EmblemSlot, emblem.EmblemItemID, true); err != nil {
return err
}
currentWriteEquipmentEmblem(data, logicalIndex, emblem.EmblemItemID)
}
return nil
}
func currentResolveEquipmentSocketRequest(rule currentEquipmentPlacementRule, openCount int, requestSocketIndex byte) (byte, bool) {
visibleOpenCount := openCount
if visibleOpenCount > 2 {
visibleOpenCount = 2
}
if requestSocketIndex >= currentAvatarSocketCount || visibleOpenCount <= 0 {
return 0, false
}
if currentEquipmentSocketOpenCount(rule) == 1 {
if requestSocketIndex > 1 {
return 0, false
}
return requestSocketIndex, true
}
if int(requestSocketIndex) >= visibleOpenCount {
return 0, false
}
return requestSocketIndex, true
}
func currentApplyAvatarEmblems(catalog *pvfDungeonDropCatalog, inventory *dnfrepo.InventoryRecord, data *[currentAvatarSocketBytes]byte, emblems []currentEmblemApplyRequest) error {
if inventory == nil || data == nil {
return errCurrentSocketInventoryMissing
}
if currentAvatarSocketOpenCount(*data) <= 0 {
return errCurrentSocketNoOpenSockets
}
for _, emblem := range emblems {
if emblem.SocketIndex >= currentAvatarSocketCount {
return fmt.Errorf("%w: socket=%d", errCurrentSocketIndexInvalid, emblem.SocketIndex)
}
socketType := currentAvatarSocketType(*data, emblem.SocketIndex)
if socketType == 0 {
return fmt.Errorf("%w: socket=%d unopened", errCurrentSocketIndexInvalid, emblem.SocketIndex)
}
emblemType := currentSocketEmblemType(catalog, emblem.EmblemItemID)
if !currentCanAttachEmblem(socketType, emblemType) {
return fmt.Errorf("%w: socket_type=0x%02x emblem_type=0x%02x emblem=%d", errCurrentSocketTypeMismatch, socketType, emblemType, emblem.EmblemItemID)
}
if _, _, err := consumeCurrentSocketStack(inventory, currentSocketListMain, emblem.EmblemSlot, emblem.EmblemItemID, true); err != nil {
return err
}
currentSetAvatarSocketEmblem(data, emblem.SocketIndex, emblem.EmblemItemID)
}
return nil
}
func consumeCurrentSocketStack(inventory *dnfrepo.InventoryRecord, listType byte, slot int16, itemID int64, requireItemID bool) (dnfrepo.ItemStack, int64, error) {
if inventory == nil {
return dnfrepo.ItemStack{}, 0, errCurrentSocketInventoryMissing
}
key := currentSocketInventoryKey(listType, slot)
stack, ok := inventory.Slots[key]
if !ok || stack.ItemID <= 0 || stack.Count <= 0 {
return dnfrepo.ItemStack{}, 0, fmt.Errorf("%w: list=%d slot=%d", errCurrentSocketEmblemMissing, listType, slot)
}
if requireItemID && stack.ItemID != itemID {
return dnfrepo.ItemStack{}, 0, fmt.Errorf("%w: list=%d slot=%d want=%d got=%d", errCurrentSocketEmblemMissing, listType, slot, itemID, stack.ItemID)
}
remaining := stack.Count - 1
if remaining > 0 {
stack.Count = remaining
currentRefreshStackRawEntry(&stack, listType, slot)
inventory.Slots[key] = stack
} else {
delete(inventory.Slots, key)
}
return stack, remaining, nil
}
func currentEquipmentEmblemData(extra map[string]string, raw []byte) [currentEquipmentEmblemDataBytes]byte {
var best [currentEquipmentEmblemDataBytes]byte
bestScore := -1
consider := func(data [currentEquipmentEmblemDataBytes]byte) {
score := currentEquipmentEmblemDataScore(data)
if score > bestScore {
best = data
bestScore = score
}
}
if data, ok := currentEquipmentEmblemDataFromExtra(extra); ok {
consider(data)
}
if data, ok := currentEquipmentEmblemDataFromCurrentRaw(raw); ok {
consider(data)
}
if tail := currentEquipmentTailDataFromExtra(extra); len(tail) >= currentEquipmentEmblemDataBytes {
var data [currentEquipmentEmblemDataBytes]byte
copy(data[:], tail[:len(data)])
consider(data)
}
if data, ok := currentEquipmentLegacyEmblemDataFromRaw(raw); ok {
consider(data)
}
return best
}
func currentEquipmentEmblemDataFromCurrentRaw(raw []byte) ([currentEquipmentEmblemDataBytes]byte, bool) {
var data [currentEquipmentEmblemDataBytes]byte
if len(raw) < currentEquipmentVectorOffset+currentEquipmentEmblemDataBytes {
return data, false
}
count := int(raw[currentEquipmentVectorOffset])
if count > 2 {
return data, false
}
data[0] = byte(count)
if count > 0 {
copy(data[1:5], raw[currentEquipmentVectorOffset+1:currentEquipmentVectorOffset+5])
}
if count > 1 {
copy(data[5:9], raw[currentEquipmentVectorOffset+5:currentEquipmentVectorOffset+9])
}
return data, true
}
func currentEquipmentEmblemDataScore(data [currentEquipmentEmblemDataBytes]byte) int {
score := 0
openCount := int(data[0])
if openCount > 2 {
return -1
}
if openCount > 0 {
score += 10 + openCount
}
if openCount > 0 && binary.LittleEndian.Uint32(data[1:5]) != 0 {
score += 10
}
if openCount > 1 && binary.LittleEndian.Uint32(data[5:9]) != 0 {
score += 10
}
return score
}
func currentEquipmentEmblemDataFromExtra(extra map[string]string) ([currentEquipmentEmblemDataBytes]byte, bool) {
var data [currentEquipmentEmblemDataBytes]byte
if len(extra) == 0 {
return data, false
}
for _, key := range currentEquipmentEmblemDataExtraKeys {
raw := strings.TrimSpace(extra[key])
if raw == "" {
continue
}
raw = strings.NewReplacer(" ", "", "\t", "", "\r", "", "\n", "", "0x", "", "0X", "").Replace(raw)
decoded, err := hex.DecodeString(raw)
if err != nil || len(decoded) < len(data) {
continue
}
copy(data[:], decoded[:len(data)])
if currentEquipmentEmblemDataScore(data) >= 0 {
return data, true
}
}
return data, false
}
func currentEquipmentLegacyEmblemDataFromRaw(raw []byte) ([currentEquipmentEmblemDataBytes]byte, bool) {
var data [currentEquipmentEmblemDataBytes]byte
if len(raw) < currentEquipmentLegacyEmblemDataOffset+currentEquipmentEmblemDataBytes {
return data, false
}
// If the current collection is already populated, row+0x2F is part of its
// second primary dword and must not be reinterpreted as old 86JP tail data.
if len(raw) >= currentEquipmentVectorOffset+1 && raw[currentEquipmentVectorOffset] > 0 {
return data, false
}
copy(data[:], raw[currentEquipmentLegacyEmblemDataOffset:currentEquipmentLegacyEmblemDataOffset+currentEquipmentEmblemDataBytes])
return data, currentEquipmentEmblemDataScore(data) >= 0
}
func currentEnsureEquipmentEmblemSocketsOpen(data *[currentEquipmentEmblemDataBytes]byte, maxOpen, openCount int) {
if data == nil || maxOpen <= 0 {
return
}
if maxOpen > 2 {
maxOpen = 2
}
visible := openCount
if visible < 0 {
visible = 0
}
if visible > maxOpen {
visible = maxOpen
}
data[0] = byte(visible)
if visible > 0 && binary.LittleEndian.Uint32(data[1:5]) == 0 {
binary.LittleEndian.PutUint32(data[1:5], math.MaxUint32)
}
if visible > 1 && binary.LittleEndian.Uint32(data[5:9]) == 0 {
binary.LittleEndian.PutUint32(data[5:9], math.MaxUint32)
}
}
func currentWriteEquipmentEmblem(data *[currentEquipmentEmblemDataBytes]byte, socketIndex byte, emblemItemID int64) {
if data == nil || emblemItemID <= 0 || emblemItemID > math.MaxUint32 {
return
}
switch socketIndex {
case 0:
binary.LittleEndian.PutUint32(data[1:5], uint32(emblemItemID))
case 1:
binary.LittleEndian.PutUint32(data[5:9], uint32(emblemItemID))
}
}
func currentApplyEquipmentEmblemDataToStack(stack *dnfrepo.ItemStack, listType byte, slot int16, data [currentEquipmentEmblemDataBytes]byte, socketType byte) {
if stack == nil {
return
}
currentEnsureExtra(&stack.Extra)
currentSetEquipmentEmblemDataExtra(stack.Extra, data)
currentSetEquipmentSocketTypeExtra(stack.Extra, socketType)
currentRefreshStackRawEntry(stack, listType, slot)
}
// currentApplyEquipmentSocketVectorToEntry writes the Extra-stored emblem data
// into the current EXE's raw[0x77] ordinary-equipment vector. sub_225CD00 reads
// count@0x3C and two contiguous u32 emblem IDs at +0x3D/+0x41; sub_22D2280
// installs every nonzero entry in the runtime equipment wrapper.
func currentApplyEquipmentSocketVectorToEntry(entry *currentItemListEntry, extra map[string]string) {
if entry == nil || len(extra) == 0 {
return
}
// Only apply to equipment items: check item_kind or equipment-specific
// Extra keys to avoid corrupting stackable/material raw rows.
if extra["item_kind"] != "equipment" &&
extra["equipment_emblem_data"] == "" && extra["equipment_socket_data"] == "" &&
extra["current_equipment_emblem_data"] == "" && extra["current_equipment_socket_data"] == "" &&
extra["equipment_socket_type"] == "" && extra["equipment_jewel_socket_type"] == "" &&
extra["current_exe_equipment_type"] == "" {
return
}
data, ok := currentEquipmentEmblemDataFromExtra(extra)
if !ok {
// Fall back to tailData2F / raw_data_2f keys (used by equipped entries
// and legacy 86JP storage).
if tail := currentEquipmentTailDataFromExtra(extra); len(tail) >= currentEquipmentEmblemDataBytes {
copy(data[:], tail[:currentEquipmentEmblemDataBytes])
ok = currentEquipmentEmblemDataScore(data) >= 0
}
}
if !ok {
return
}
count := int(data[0])
if count <= 0 {
return
}
if count > 2 {
count = 2
}
currentClearLegacyWrongEquipmentSocketProjection(entry, data, count)
// Current EXE ordinary-equipment emblem vector at raw+0x3C.
entry.data[currentEquipmentVectorOffset] = byte(count)
// emblem[0] (0xFFFFFFFF = open and empty).
copy(entry.data[currentEquipmentVectorOffset+1:currentEquipmentVectorOffset+5], data[1:5])
binary.LittleEndian.PutUint32(entry.data[currentEquipmentVectorOffset+5:currentEquipmentVectorOffset+9], 0)
if count > 1 {
copy(entry.data[currentEquipmentVectorOffset+5:currentEquipmentVectorOffset+9], data[5:9])
}
}
// currentClearLegacyWrongEquipmentSocketProjection removes only the exact byte
// pattern emitted by the interim raw+0x27 projection. A nonmatching record is
// preserved because raw+0x27 belongs to a separate current-client collection.
func currentClearLegacyWrongEquipmentSocketProjection(entry *currentItemListEntry, data [currentEquipmentEmblemDataBytes]byte, count int) {
if entry == nil || count <= 0 || count > 2 {
return
}
offset := currentLegacyWrongEquipmentVectorOffset
if entry.data[offset] != byte(count) ||
!bytes.Equal(entry.data[offset+1:offset+5], data[1:5]) {
return
}
var second [4]byte
if count > 1 {
copy(second[:], data[5:9])
}
if !bytes.Equal(entry.data[offset+5:offset+9], second[:]) {
return
}
for _, value := range entry.data[offset+9 : offset+17] {
if value != 0 {
return
}
}
clear(entry.data[offset : offset+17])
}
func currentApplyEquipmentEmblemDataToEquipment(entry *dnfrepo.EquipmentEntry, data [currentEquipmentEmblemDataBytes]byte, socketType byte) {
if entry == nil {
return
}
currentEnsureExtra(&entry.Extra)
currentSetEquipmentEmblemDataExtra(entry.Extra, data)
currentSetEquipmentSocketTypeExtra(entry.Extra, socketType)
if row, ok := currentItemListEntryFromEquipment(*entry); ok {
entry.RawEntry = append([]byte(nil), row.data[:]...)
}
}
func currentEquipmentTailData(extra map[string]string, raw []byte) []byte {
tail := currentEquipmentTailDataFromExtra(extra)
if len(tail) == currentEquipmentTailDataBytes {
return tail
}
tail = make([]byte, currentEquipmentTailDataBytes)
if len(raw) >= currentEquipmentLegacyEmblemDataOffset+len(tail) && (len(raw) <= currentEquipmentVectorOffset || raw[currentEquipmentVectorOffset] == 0) {
copy(tail, raw[currentEquipmentLegacyEmblemDataOffset:currentEquipmentLegacyEmblemDataOffset+len(tail)])
}
return tail
}
func currentEquipmentTailDataFromExtra(extra map[string]string) []byte {
if len(extra) == 0 {
return nil
}
var fallback []byte
for _, key := range currentEquipmentTailDataExtraKeys {
raw := strings.TrimSpace(extra[key])
if raw == "" {
continue
}
raw = strings.NewReplacer(" ", "", "\t", "", "\r", "", "\n", "", "0x", "", "0X", "").Replace(raw)
decoded, err := hex.DecodeString(raw)
if err != nil || len(decoded) == 0 {
continue
}
out := make([]byte, currentEquipmentTailDataBytes)
copy(out, decoded)
if currentEquipmentTailHasSocketData(out) {
return out
}
if fallback == nil {
fallback = out
}
}
return fallback
}
func currentEquipmentTailHasSocketData(tail []byte) bool {
if len(tail) == 0 {
return false
}
if tail[0] != 0 {
return true
}
if len(tail) >= currentEquipmentEmblemDataBytes {
return binary.LittleEndian.Uint32(tail[1:5]) != 0 || binary.LittleEndian.Uint32(tail[5:9]) != 0
}
for _, value := range tail {
if value != 0 {
return true
}
}
return false
}
func currentSetEquipmentTailDataExtra(extra map[string]string, tail []byte) {
if extra == nil {
return
}
for _, key := range currentEquipmentTailDataExtraKeys {
currentSetHexExtra(extra, key, tail)
}
}
func currentSetEquipmentEmblemDataExtra(extra map[string]string, data [currentEquipmentEmblemDataBytes]byte) {
if extra == nil {
return
}
for _, key := range currentEquipmentEmblemDataExtraKeys {
currentSetHexExtra(extra, key, data[:])
}
}
func currentSetEquipmentSocketTypeExtra(extra map[string]string, socketType byte) {
if extra == nil || socketType == 0 {
return
}
value := strconv.Itoa(int(socketType))
extra["equipment_socket_type"] = value
extra["equipment_jewel_socket_type"] = value
}
func currentEquipmentSocketTypeFromExtra(extra map[string]string) byte {
if value := sceneInventoryExtraByte(extra, "equipment_socket_type", "equipment_jewel_socket_type", "jewel_socket_type"); value != 0 {
return value
}
if value, ok := sceneInventoryExtraUint(extra, "current_exe_equipment_type", "equipped_slot", "equipment_slot"); ok && value <= math.MaxInt16 {
return currentEquipmentJewelSocketType(currentEquipmentPlacementRule{targetSlot: int16(value)})
}
return 0
}
func currentAvatarSocketData(extra map[string]string) [currentAvatarSocketBytes]byte {
var data [currentAvatarSocketBytes]byte
if value := currentItemListAvatarSocketData(extra); len(value) == currentAvatarSocketBytes {
copy(data[:], value)
}
return currentNormalizeAvatarSocketData(data)
}
func currentNormalizeAvatarSocketData(data [currentAvatarSocketBytes]byte) [currentAvatarSocketBytes]byte {
for index := 0; index < currentAvatarSocketCount; index++ {
offset := index * currentAvatarSocketStride
if data[offset] == 0 && currentKnownAvatarSocketType(data[offset+1]) {
var normalized [currentAvatarSocketBytes]byte
for inner := 0; inner < currentAvatarSocketCount; inner++ {
src := inner * currentAvatarSocketStride
if data[src] == 0 && currentKnownAvatarSocketType(data[src+1]) {
normalized[src] = data[src+1]
normalized[src+1] = data[src+2]
copy(normalized[src+2:src+5], data[src+3:src+6])
continue
}
copy(normalized[src:src+currentAvatarSocketStride], data[src:src+currentAvatarSocketStride])
}
data = normalized
break
}
}
// The current client represents the universal M socket as the special
// ushort 0xFFEF. The low byte is still the PVF socket-family value, but
// 0x00EF is rejected by the avatar tooltip as a closed/invalid socket.
// Normalize rows emitted by the earlier server build while they are read,
// so already-granted auras become visible without granting the item again.
for index := 0; index < currentAvatarSocketCount; index++ {
offset := index * currentAvatarSocketStride
if binary.LittleEndian.Uint16(data[offset:offset+2]) == 0x00EF {
binary.LittleEndian.PutUint16(data[offset:offset+2], 0xFFEF)
}
}
return data
}
func currentKnownAvatarSocketType(socketType byte) bool {
switch socketType {
case 0x01, 0x02, 0x04, 0x08, 0x10, 0xEF:
return true
default:
return false
}
}
func currentAvatarSocketOpenCount(data [currentAvatarSocketBytes]byte) int {
count := 0
for index := 0; index < currentAvatarSocketCount; index++ {
if currentAvatarSocketType(data, byte(index)) != 0 {
count++
}
}
return count
}
func currentAvatarSocketType(data [currentAvatarSocketBytes]byte, socketIndex byte) byte {
if socketIndex >= currentAvatarSocketCount {
return 0
}
return data[int(socketIndex)*currentAvatarSocketStride]
}
func currentSetAvatarSocketTypes(data *[currentAvatarSocketBytes]byte, socketTypes []byte) {
if data == nil {
return
}
for index := 0; index < currentAvatarSocketCount; index++ {
offset := index * currentAvatarSocketStride
if index < len(socketTypes) {
socketType := uint16(socketTypes[index])
if socketTypes[index] == 0xEF {
socketType = 0xFFEF
}
binary.LittleEndian.PutUint16(data[offset:offset+2], socketType)
continue
}
for pos := 0; pos < currentAvatarSocketStride; pos++ {
data[offset+pos] = 0
}
}
}
func currentSetAvatarSocketEmblem(data *[currentAvatarSocketBytes]byte, socketIndex byte, emblemItemID int64) {
if data == nil || socketIndex >= currentAvatarSocketCount || emblemItemID <= 0 || emblemItemID > math.MaxUint32 {
return
}
offset := int(socketIndex)*currentAvatarSocketStride + 2
binary.LittleEndian.PutUint32(data[offset:offset+4], uint32(emblemItemID))
}
func currentApplyAvatarSocketDataToStack(stack *dnfrepo.ItemStack, listType byte, slot int16, data [currentAvatarSocketBytes]byte) {
if stack == nil {
return
}
currentEnsureExtra(&stack.Extra)
currentSetHexExtra(stack.Extra, "avatar_socket_data", data[:])
currentSetHexExtra(stack.Extra, "reserved2", data[:])
currentSetHexExtra(stack.Extra, "jewel_socket", data[:])
currentRefreshStackRawEntry(stack, listType, slot)
}
func currentApplyAvatarSocketDataToEquipment(entry *dnfrepo.EquipmentEntry, data [currentAvatarSocketBytes]byte) {
if entry == nil {
return
}
currentEnsureExtra(&entry.Extra)
currentSetHexExtra(entry.Extra, "avatar_socket_data", data[:])
currentSetHexExtra(entry.Extra, "reserved2", data[:])
currentSetHexExtra(entry.Extra, "jewel_socket", data[:])
if row, ok := currentItemListEntryFromEquipment(*entry); ok {
entry.RawEntry = append([]byte(nil), row.data[:]...)
}
}
func currentRefreshStackRawEntry(stack *dnfrepo.ItemStack, listType byte, slot int16) {
if stack == nil || stack.ItemID <= 0 {
return
}
entry := currentItemListEntryFromStack(listType, slot, *stack)
stack.RawEntry = append([]byte(nil), entry.data[:]...)
}
func currentEnsureExtra(extra *map[string]string) {
if extra == nil {
return
}
if *extra == nil {
*extra = make(map[string]string)
}
}
func currentSetHexExtra(extra map[string]string, key string, data []byte) {
if extra == nil || key == "" {
return
}
extra[key] = hex.EncodeToString(data)
}
func currentEmblemConsumedSlots(emblems []currentEmblemApplyRequest) []currentSocketChangedSlot {
seen := make(map[int16]struct{}, len(emblems))
out := make([]currentSocketChangedSlot, 0, len(emblems))
for _, emblem := range emblems {
if _, ok := seen[emblem.EmblemSlot]; ok {
continue
}
seen[emblem.EmblemSlot] = struct{}{}
out = append(out, currentSocketChangedSlot{ListType: currentSocketListMain, Slot: emblem.EmblemSlot})
}
return out
}
func currentFindEquippedEntry(record dnfrepo.EquipmentRecord, requestedSlot int16, itemID int64, class currentEquipmentPlacementClass) (string, dnfrepo.EquipmentEntry, bool) {
for key, entry := range record.Entries {
if entry.ItemID != itemID || entry.ItemID <= 0 {
continue
}
entryClass := currentEquippedEntryPlacementClass(entry)
if entryClass != class {
continue
}
if entry.SlotIndex == requestedSlot {
return key, entry, true
}
if actorSlot, ok := currentEXEActorEquipmentSlot(entry); ok && int16(actorSlot) == requestedSlot {
return key, entry, true
}
}
return "", dnfrepo.EquipmentEntry{}, false
}
func currentEquippedEntryPlacementClass(entry dnfrepo.EquipmentEntry) currentEquipmentPlacementClass {
if actorSlot, ok := currentEXEActorEquipmentSlot(entry); ok {
switch {
case actorSlot <= uint64(currentEquipmentPlacementAvatarSlotMax):
return currentEquipmentPlacementClassAvatar
case actorSlot >= uint64(currentEquipmentPlacementNormalSlotMin) && actorSlot <= uint64(currentEquipmentPlacementNormalSlotMax):
return currentEquipmentPlacementClassNormal
}
}
entryClass, err := currentEquipmentPlacementClassForSource(currentSocketListEquipment, entry.SlotIndex)
if err != nil {
return currentEquipmentPlacementClassUnknown
}
return entryClass
}
func currentSocketInventoryKey(listType byte, slot int16) string {
return strconv.Itoa(int(listType)) + ":" + strconv.FormatInt(int64(slot), 10)
}