服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
179 lines
5.1 KiB
Go
179 lines
5.1 KiB
Go
package dnfrobot
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
BatchActionStart = "start"
|
|
BatchActionStop = "stop"
|
|
BatchActionMove = "move"
|
|
BatchActionChat = "chat"
|
|
BatchActionStoreOpen = "store_open"
|
|
BatchActionStoreClose = "store_close"
|
|
)
|
|
|
|
type BatchRequest struct {
|
|
Action string `json:"action"`
|
|
IDs []string `json:"ids,omitempty"`
|
|
Village uint8 `json:"village,omitempty"`
|
|
Area uint8 `json:"area,omitempty"`
|
|
X uint16 `json:"x,omitempty"`
|
|
Y uint16 `json:"y,omitempty"`
|
|
MoveType uint8 `json:"move_type,omitempty"`
|
|
ChatMode uint8 `json:"chat_mode,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
StoreTitle string `json:"store_title,omitempty"`
|
|
StoreRows int `json:"store_rows,omitempty"`
|
|
StorePrice uint32 `json:"store_price,omitempty"`
|
|
}
|
|
|
|
type BatchActionResult struct {
|
|
ID string `json:"id"`
|
|
OK bool `json:"ok"`
|
|
Error string `json:"error,omitempty"`
|
|
Snapshot RuntimeSnapshot `json:"snapshot"`
|
|
}
|
|
|
|
type BatchResult struct {
|
|
Action string `json:"action"`
|
|
Requested int `json:"requested"`
|
|
Succeeded int `json:"succeeded"`
|
|
Failed int `json:"failed"`
|
|
Results []BatchActionResult `json:"results"`
|
|
}
|
|
|
|
type ManagerStatus struct {
|
|
Configured int `json:"configured"`
|
|
Running int `json:"running"`
|
|
Connecting int `json:"connecting"`
|
|
TownReady int `json:"town_ready"`
|
|
StoreReady int `json:"store_ready"`
|
|
Pending int `json:"pending"`
|
|
Errors int `json:"errors"`
|
|
Reconnects int `json:"reconnects"`
|
|
TargetOnline int `json:"target_online"`
|
|
SchedulerManaged int `json:"scheduler_managed"`
|
|
}
|
|
|
|
func (m *Manager) Status() ManagerStatus {
|
|
status := ManagerStatus{TargetOnline: m.Automation().TargetOnline}
|
|
for _, snapshot := range m.List() {
|
|
status.Configured++
|
|
status.Reconnects += snapshot.Reconnects
|
|
if snapshot.Running {
|
|
status.Running++
|
|
}
|
|
if snapshot.SchedulerManaged {
|
|
status.SchedulerManaged++
|
|
}
|
|
switch snapshot.State {
|
|
case StateConnecting, StateBinding, StateSelecting:
|
|
status.Connecting++
|
|
case StateTownReady:
|
|
status.TownReady++
|
|
case StateStoreReady:
|
|
status.StoreReady++
|
|
case StateStoreOpening, StateStoreClosing:
|
|
status.Pending++
|
|
case StateError:
|
|
status.Errors++
|
|
}
|
|
if snapshot.LastError != "" && snapshot.State != StateError {
|
|
status.Errors++
|
|
}
|
|
}
|
|
return status
|
|
}
|
|
|
|
func (m *Manager) Batch(ctx context.Context, request BatchRequest) (BatchResult, error) {
|
|
if m == nil {
|
|
return BatchResult{}, ErrManagerStopped
|
|
}
|
|
action := strings.ToLower(strings.TrimSpace(request.Action))
|
|
switch action {
|
|
case BatchActionStart, BatchActionStop, BatchActionMove, BatchActionChat, BatchActionStoreOpen, BatchActionStoreClose:
|
|
default:
|
|
return BatchResult{}, errors.New("invalid dnf robot batch action")
|
|
}
|
|
ids := m.batchProfileIDs(request.IDs)
|
|
if len(ids) == 0 {
|
|
return BatchResult{}, ErrProfileNotFound
|
|
}
|
|
operation := m.BeginOperation("batch", action)
|
|
result := BatchResult{Action: action, Requested: len(ids), Results: make([]BatchActionResult, 0, len(ids))}
|
|
for _, id := range ids {
|
|
var (
|
|
snapshot RuntimeSnapshot
|
|
err error
|
|
)
|
|
switch action {
|
|
case BatchActionStart:
|
|
snapshot, err = m.StartProfile(id)
|
|
case BatchActionStop:
|
|
snapshot, err = m.StopProfile(id)
|
|
case BatchActionMove:
|
|
err = m.Move(id, request.Village, request.Area, request.X, request.Y, request.MoveType)
|
|
snapshot = m.snapshotByID(id)
|
|
case BatchActionChat:
|
|
err = m.Chat(id, request.ChatMode, request.Message)
|
|
snapshot = m.snapshotByID(id)
|
|
case BatchActionStoreOpen:
|
|
err = m.OpenPrivateStore(ctx, id, request.StoreTitle, request.StoreRows, request.StorePrice)
|
|
snapshot = m.snapshotByID(id)
|
|
case BatchActionStoreClose:
|
|
err = m.ClosePrivateStore(id)
|
|
snapshot = m.snapshotByID(id)
|
|
}
|
|
row := BatchActionResult{ID: id, OK: err == nil, Snapshot: snapshot}
|
|
if err != nil {
|
|
row.Error = err.Error()
|
|
result.Failed++
|
|
} else {
|
|
result.Succeeded++
|
|
}
|
|
result.Results = append(result.Results, row)
|
|
}
|
|
m.CompleteOperation(operation.ID, fmt.Sprintf("requested=%d succeeded=%d failed=%d", result.Requested, result.Succeeded, result.Failed), nil)
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Manager) batchProfileIDs(requested []string) []string {
|
|
if len(requested) == 0 {
|
|
m.mu.RLock()
|
|
ids := make([]string, 0, len(m.config.Profiles))
|
|
for _, profile := range m.config.Profiles {
|
|
ids = append(ids, profile.ID)
|
|
}
|
|
m.mu.RUnlock()
|
|
return ids
|
|
}
|
|
seen := make(map[string]struct{}, len(requested))
|
|
ids := make([]string, 0, len(requested))
|
|
for _, id := range requested {
|
|
id = strings.TrimSpace(id)
|
|
key := profileKey(id)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
if _, exists := seen[key]; exists {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
ids = append(ids, id)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func (m *Manager) snapshotByID(id string) RuntimeSnapshot {
|
|
for _, snapshot := range m.List() {
|
|
if profileKey(snapshot.Profile.ID) == profileKey(id) {
|
|
return snapshot
|
|
}
|
|
}
|
|
return RuntimeSnapshot{}
|
|
}
|