服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
286 lines
8.7 KiB
Go
286 lines
8.7 KiB
Go
package contracts
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// TopicScope 表示带作用域 topic 的层级类型。
|
|
type TopicScope string
|
|
|
|
const (
|
|
// TopicScopeService 表示服务作用域 topic。
|
|
TopicScopeService TopicScope = "service"
|
|
|
|
// TopicScopeZone 表示区服作用域 topic。
|
|
TopicScopeZone TopicScope = "zone"
|
|
|
|
// TopicScopeShard 表示分片作用域 topic。
|
|
TopicScopeShard TopicScope = "shard"
|
|
|
|
// TopicServiceStarted 是单节点服务启动事件 topic。
|
|
TopicServiceStarted = "service.started"
|
|
|
|
// TopicClusterServiceStarted 是集群服务启动事件 topic。
|
|
TopicClusterServiceStarted = "cluster.service.started"
|
|
|
|
// TopicClusterServiceState 是集群服务状态变化事件 topic。
|
|
TopicClusterServiceState = "cluster.service.state"
|
|
|
|
// TopicClusterServiceBeat 是集群服务心跳事件 topic。
|
|
TopicClusterServiceBeat = "cluster.service.heartbeat"
|
|
|
|
// TopicClusterServiceStopped 是集群服务停止事件 topic。
|
|
TopicClusterServiceStopped = "cluster.service.stopped"
|
|
|
|
// TopicGatewayClientPacket 是 gateway 收到客户端包后的事件 topic。
|
|
TopicGatewayClientPacket = "gateway.client.packet"
|
|
|
|
// TopicGatewayPush 是推送到 gateway 的消息 topic。
|
|
TopicGatewayPush = "gateway.push"
|
|
|
|
// TopicLogicPlayerResponse 是 logic 返回玩家响应的事件 topic。
|
|
TopicLogicPlayerResponse = "logic.player.response"
|
|
|
|
// TopicSessionConnected 是会话上线事件 topic。
|
|
TopicSessionConnected = "gateway.session.connected"
|
|
|
|
// TopicSessionDisconnected 是会话下线事件 topic。
|
|
TopicSessionDisconnected = "gateway.session.disconnected"
|
|
|
|
// TopicPlayerStateChange 是玩家状态变化事件 topic。
|
|
TopicPlayerStateChange = "logic.player.state.changed"
|
|
|
|
// TopicChatBroadcast 是聊天广播事件 topic。
|
|
TopicChatBroadcast = "social.chat.broadcast"
|
|
|
|
// TopicNoticePublished 是公告发布事件 topic。
|
|
TopicNoticePublished = "notice.published"
|
|
|
|
// TopicRoomAllocated 是场景房间分配事件 topic。
|
|
TopicRoomAllocated = "scene.room.allocated"
|
|
|
|
topicLogicPrefix = "logic.node."
|
|
logicPacketSuffix = ".packet"
|
|
sessionOnSuffix = ".session.connected"
|
|
sessionOffSuffix = ".session.disconnected"
|
|
topicGwNodePrefix = "gateway.node."
|
|
topicGwPushSuffix = ".push"
|
|
topicGwRespSuffix = ".response"
|
|
topicRPCNodePrefix = "rpc.node."
|
|
topicRPCReqSuffix = ".request"
|
|
topicRPCRespPrefix = "rpc.response."
|
|
topicRPCRespSuffix = ".reply"
|
|
)
|
|
|
|
// ScopedTopic 是解析后的作用域 topic 结构。
|
|
type ScopedTopic struct {
|
|
Scope TopicScope `json:"scope"`
|
|
Service string `json:"service,omitempty"`
|
|
ZoneID string `json:"zone_id,omitempty"`
|
|
GroupID string `json:"group_id,omitempty"`
|
|
ShardID string `json:"shard_id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
// LogicNodePacketTopic 返回指定 logic 节点接收客户端包的 topic。
|
|
func LogicNodePacketTopic(nodeID string) string {
|
|
return logicNodeTopic(nodeID, logicPacketSuffix)
|
|
}
|
|
|
|
// LogicSessConnTopic 返回指定 logic 节点接收会话上线事件的 topic。
|
|
func LogicSessConnTopic(nodeID string) string {
|
|
return logicNodeTopic(nodeID, sessionOnSuffix)
|
|
}
|
|
|
|
// LogicSessDiscTopic 返回指定 logic 节点接收会话下线事件的 topic。
|
|
func LogicSessDiscTopic(nodeID string) string {
|
|
return logicNodeTopic(nodeID, sessionOffSuffix)
|
|
}
|
|
|
|
// IsLogicNodePacketTopic 判断 topic 是否为 logic 节点客户端包 topic。
|
|
func IsLogicNodePacketTopic(topic string) bool {
|
|
return isLogicNodeTopic(topic, logicPacketSuffix)
|
|
}
|
|
|
|
// IsLogicSessConnTopic 判断 topic 是否为 logic 节点会话上线 topic。
|
|
func IsLogicSessConnTopic(topic string) bool {
|
|
return isLogicNodeTopic(topic, sessionOnSuffix)
|
|
}
|
|
|
|
// IsLogicSessDiscTopic 判断 topic 是否为 logic 节点会话下线 topic。
|
|
func IsLogicSessDiscTopic(topic string) bool {
|
|
return isLogicNodeTopic(topic, sessionOffSuffix)
|
|
}
|
|
|
|
// GatewayNodePushTopic 返回指定 gateway 节点接收推送的 topic。
|
|
func GatewayNodePushTopic(nodeID string) string {
|
|
return nodeTopic(topicGwNodePrefix, nodeID, topicGwPushSuffix)
|
|
}
|
|
|
|
// IsGatewayNodePushTopic 判断 topic 是否为 gateway 节点推送 topic。
|
|
func IsGatewayNodePushTopic(topic string) bool {
|
|
return isNodeTopic(topic, topicGwNodePrefix, topicGwPushSuffix)
|
|
}
|
|
|
|
// GatewayNodeRespTopic 返回指定 gateway 节点接收 logic 玩家响应的 topic。
|
|
func GatewayNodeRespTopic(nodeID string) string {
|
|
return nodeTopic(topicGwNodePrefix, nodeID, topicGwRespSuffix)
|
|
}
|
|
|
|
// IsGatewayNodeRespTopic 判断 topic 是否为 gateway 节点玩家响应 topic。
|
|
func IsGatewayNodeRespTopic(topic string) bool {
|
|
return isNodeTopic(topic, topicGwNodePrefix, topicGwRespSuffix)
|
|
}
|
|
|
|
// RPCNodeRequestTopic 返回指定节点接收 RPC 请求的 topic。
|
|
func RPCNodeRequestTopic(nodeID string) string {
|
|
return nodeTopic(topicRPCNodePrefix, nodeID, topicRPCReqSuffix)
|
|
}
|
|
|
|
// RPCResponseTopic 返回指定节点接收 RPC 响应的 topic。
|
|
func RPCResponseTopic(nodeID string) string {
|
|
return nodeTopic(topicRPCRespPrefix, nodeID, topicRPCRespSuffix)
|
|
}
|
|
|
|
// IsRPCNodeRequestTopic 判断 topic 是否为节点 RPC 请求 topic。
|
|
func IsRPCNodeRequestTopic(topic string) bool {
|
|
return isNodeTopic(topic, topicRPCNodePrefix, topicRPCReqSuffix)
|
|
}
|
|
|
|
// IsRPCResponseTopic 判断 topic 是否为节点 RPC 响应 topic。
|
|
func IsRPCResponseTopic(topic string) bool {
|
|
return isNodeTopic(topic, topicRPCRespPrefix, topicRPCRespSuffix)
|
|
}
|
|
|
|
// ServiceScopedTopic 构造服务作用域 topic。
|
|
func ServiceScopedTopic(service string, groupID string, shardID string, name string) string {
|
|
return scopedTopic("service", service, groupID, shardID, name)
|
|
}
|
|
|
|
// ZoneScopedTopic 构造区服作用域 topic。
|
|
func ZoneScopedTopic(zoneID string, name string) string {
|
|
return scopedTopic("zone", zoneID, "", "", name)
|
|
}
|
|
|
|
// ShardScopedTopic 构造分片作用域 topic。
|
|
func ShardScopedTopic(shardID string, name string) string {
|
|
return scopedTopic("shard", shardID, "", "", name)
|
|
}
|
|
|
|
// ParseScopedTopic 解析服务、区服或分片作用域 topic。
|
|
func ParseScopedTopic(topic string) (ScopedTopic, bool) {
|
|
parts := strings.Split(strings.TrimSpace(topic), ".")
|
|
if len(parts) < 2 || hasEmptyPart(parts) {
|
|
return ScopedTopic{}, false
|
|
}
|
|
switch TopicScope(parts[0]) {
|
|
case TopicScopeService:
|
|
return parseScopedTopic(parts)
|
|
case TopicScopeZone:
|
|
return parseOneScopedTopic(TopicScopeZone, parts)
|
|
case TopicScopeShard:
|
|
return parseOneScopedTopic(TopicScopeShard, parts)
|
|
default:
|
|
return ScopedTopic{}, false
|
|
}
|
|
}
|
|
|
|
func logicNodeTopic(nodeID, suffix string) string {
|
|
return nodeTopic(topicLogicPrefix, nodeID, suffix)
|
|
}
|
|
|
|
func nodeTopic(prefix, nodeID, suffix string) string {
|
|
nodeID = strings.TrimSpace(nodeID)
|
|
if nodeID == "" {
|
|
return ""
|
|
}
|
|
return prefix + nodeID + suffix
|
|
}
|
|
|
|
func isLogicNodeTopic(topic, suffix string) bool {
|
|
return isNodeTopic(topic, topicLogicPrefix, suffix)
|
|
}
|
|
|
|
func isNodeTopic(topic, prefix, suffix string) bool {
|
|
if prefix == "" || suffix == "" {
|
|
return false
|
|
}
|
|
if !strings.HasPrefix(topic, prefix) || !strings.HasSuffix(topic, suffix) {
|
|
return false
|
|
}
|
|
nodeID := strings.TrimSuffix(strings.TrimPrefix(topic, prefix), suffix)
|
|
return strings.TrimSpace(nodeID) != ""
|
|
}
|
|
|
|
func scopedTopic(scope string, first string, groupID string, shardID string, name string) string {
|
|
parts := []string{strings.TrimSpace(scope), strings.TrimSpace(first)}
|
|
if groupID = strings.TrimSpace(groupID); groupID != "" {
|
|
parts = append(parts, "gid", groupID)
|
|
}
|
|
if shardID = strings.TrimSpace(shardID); shardID != "" {
|
|
parts = append(parts, "sid", shardID)
|
|
}
|
|
if name = strings.TrimSpace(name); name != "" {
|
|
parts = append(parts, name)
|
|
}
|
|
for _, part := range parts {
|
|
if part == "" {
|
|
return ""
|
|
}
|
|
}
|
|
return strings.Join(parts, ".")
|
|
}
|
|
|
|
func parseScopedTopic(parts []string) (ScopedTopic, bool) {
|
|
if len(parts) < 2 {
|
|
return ScopedTopic{}, false
|
|
}
|
|
out := ScopedTopic{Scope: TopicScopeService, Service: parts[1]}
|
|
idx := 2
|
|
for idx < len(parts) {
|
|
switch parts[idx] {
|
|
case "gid":
|
|
if idx+1 >= len(parts) || out.GroupID != "" {
|
|
return ScopedTopic{}, false
|
|
}
|
|
out.GroupID = parts[idx+1]
|
|
idx += 2
|
|
case "sid":
|
|
if idx+1 >= len(parts) || out.ShardID != "" {
|
|
return ScopedTopic{}, false
|
|
}
|
|
out.ShardID = parts[idx+1]
|
|
idx += 2
|
|
default:
|
|
out.Name = strings.Join(parts[idx:], ".")
|
|
return out, true
|
|
}
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
func parseOneScopedTopic(scope TopicScope, parts []string) (ScopedTopic, bool) {
|
|
if len(parts) < 2 {
|
|
return ScopedTopic{}, false
|
|
}
|
|
out := ScopedTopic{Scope: scope}
|
|
if scope == TopicScopeZone {
|
|
out.ZoneID = parts[1]
|
|
} else {
|
|
out.ShardID = parts[1]
|
|
}
|
|
if len(parts) > 2 {
|
|
out.Name = strings.Join(parts[2:], ".")
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
func hasEmptyPart(parts []string) bool {
|
|
for _, part := range parts {
|
|
if strings.TrimSpace(part) == "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|