2026-06-18 18:48:47 +08:00

208 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gamemanagement
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
activityv1 "hyapp.local/api/proto/activity/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
)
const (
robotResourceDurationDays = 9999
robotResourceDurationMS = int64(robotResourceDurationDays) * int64(24*time.Hour/time.Millisecond)
robotMinDisplayLevel = 5
robotMaxDisplayLevel = 30
)
var excludedRobotAvatarFrameResourceIDs = map[int64]struct{}{
165: {},
175: {},
}
type robotAppearanceCatalog struct {
avatarFrames []*walletv1.Resource
vehicles []*walletv1.Resource
longBadges []*walletv1.Resource
shortBadges []*walletv1.Resource
}
func (h *Handler) initializeGeneratedRobotAppearance(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64) error {
if h.wallet == nil || h.activity == nil {
return fmt.Errorf("机器人装扮初始化依赖未配置")
}
catalog, err := h.loadRobotAppearanceCatalog(ctx, requestID, appCode)
if err != nil {
return err
}
avatarFrame := randomRobotResource(catalog.avatarFrames)
vehicle := randomRobotResource(catalog.vehicles)
longBadge := randomRobotResource(catalog.longBadges)
shortBadge := randomRobotResource(catalog.shortBadges)
if avatarFrame == nil || vehicle == nil || longBadge == nil || shortBadge == nil {
return fmt.Errorf("机器人装扮资源库不完整: 头像框=%d 座驾=%d 长徽章=%d 短徽章=%d", len(catalog.avatarFrames), len(catalog.vehicles), len(catalog.longBadges), len(catalog.shortBadges))
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "avatar_frame", avatarFrame.GetResourceId(), true); err != nil {
return err
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "vehicle", vehicle.GetResourceId(), true); err != nil {
return err
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "long_badge", longBadge.GetResourceId(), false); err != nil {
return err
}
if err := h.grantAndEquipRobotResource(ctx, requestID, appCode, actorUserID, userID, "short_badge", shortBadge.GetResourceId(), false); err != nil {
return err
}
// 财富和魅力等级由 activity-service 按等级规则写入账户、展示投影和等级奖励 job
// admin-server 只给出目标等级,避免绕过成长系统直接伪造资料卡字段。
if err := h.setRobotDisplayLevel(ctx, requestID, appCode, actorUserID, userID, "wealth", int32(robotMinDisplayLevel+randomRobotInt(robotMaxDisplayLevel-robotMinDisplayLevel+1))); err != nil {
return err
}
if err := h.setRobotDisplayLevel(ctx, requestID, appCode, actorUserID, userID, "charm", int32(robotMinDisplayLevel+randomRobotInt(robotMaxDisplayLevel-robotMinDisplayLevel+1))); err != nil {
return err
}
return nil
}
func (h *Handler) loadRobotAppearanceCatalog(ctx context.Context, requestID string, appCode string) (robotAppearanceCatalog, error) {
avatarFrames, err := h.listRobotResources(ctx, requestID, appCode, "avatar_frame")
if err != nil {
return robotAppearanceCatalog{}, err
}
vehicles, err := h.listRobotResources(ctx, requestID, appCode, "vehicle")
if err != nil {
return robotAppearanceCatalog{}, err
}
badges, err := h.listRobotResources(ctx, requestID, appCode, "badge")
if err != nil {
return robotAppearanceCatalog{}, err
}
catalog := robotAppearanceCatalog{avatarFrames: avatarFrames, vehicles: vehicles}
for _, badge := range badges {
switch robotBadgeForm(badge.GetMetadataJson()) {
case "strip", "long":
catalog.longBadges = append(catalog.longBadges, badge)
case "tile", "short":
catalog.shortBadges = append(catalog.shortBadges, badge)
}
}
return catalog, nil
}
func (h *Handler) listRobotResources(ctx context.Context, requestID string, appCode string, resourceType string) ([]*walletv1.Resource, error) {
resp, err := h.wallet.ListResources(ctx, &walletv1.ListResourcesRequest{
RequestId: requestID,
AppCode: appCode,
ResourceType: resourceType,
Page: 1,
PageSize: 500,
ActiveOnly: true,
})
if err != nil {
return nil, fmt.Errorf("读取机器人%s资源失败: %w", resourceType, err)
}
resources := make([]*walletv1.Resource, 0, len(resp.GetResources()))
for _, item := range resp.GetResources() {
if item == nil || item.GetResourceId() <= 0 || item.GetStatus() != "active" {
continue
}
if isExcludedRobotAppearanceResource(resourceType, item.GetResourceId()) {
// 机器人装扮随机池只过滤明确不适合机器人批量露出的资源;资源仍保留在 wallet owner 中,
// 真人发放、商城和其他后台场景不受这个创建机器人专用策略影响。
continue
}
resources = append(resources, item)
}
return resources, nil
}
func isExcludedRobotAppearanceResource(resourceType string, resourceID int64) bool {
if resourceType != "avatar_frame" {
return false
}
_, excluded := excludedRobotAvatarFrameResourceIDs[resourceID]
return excluded
}
func (h *Handler) grantAndEquipRobotResource(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64, slot string, resourceID int64, equip bool) error {
grant, err := h.wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{
CommandId: fmt.Sprintf("game_robot_init:%s:%d:%s:%d", requestID, userID, slot, resourceID),
AppCode: appCode,
TargetUserId: userID,
ResourceId: resourceID,
Quantity: 1,
DurationMs: robotResourceDurationMS,
Reason: "game_robot_init",
OperatorUserId: actorUserID,
GrantSource: "game_robot_init",
})
if err != nil {
return fmt.Errorf("发放机器人%s失败: %w", slot, err)
}
if !equip {
return nil
}
entitlementID := ""
for _, item := range grant.GetGrant().GetItems() {
if item.GetResourceId() == resourceID {
entitlementID = item.GetEntitlementId()
break
}
}
if _, err := h.wallet.EquipUserResource(ctx, &walletv1.EquipUserResourceRequest{
RequestId: fmt.Sprintf("%s:%s:equip", requestID, slot),
AppCode: appCode,
UserId: userID,
ResourceId: resourceID,
EntitlementId: entitlementID,
}); err != nil {
return fmt.Errorf("装备机器人%s失败: %w", slot, err)
}
return nil
}
func (h *Handler) setRobotDisplayLevel(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64, track string, level int32) error {
_, err := h.activity.SetUserLevel(ctx, &activityv1.SetUserLevelRequest{
Meta: activityRequestMeta(requestID, appCode),
CommandId: fmt.Sprintf("game_robot_init:%s:%d:%s:%d", requestID, userID, track, level),
UserId: userID,
Track: track,
Level: level,
OperatorUserId: actorUserID,
Reason: "game_robot_init",
})
if err != nil {
return fmt.Errorf("设置机器人%s等级失败: %w", track, err)
}
return nil
}
func robotBadgeForm(metadataJSON string) string {
var payload map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &payload); err != nil {
return ""
}
value, _ := payload["badge_form"].(string)
return strings.ToLower(strings.TrimSpace(value))
}
func randomRobotResource(items []*walletv1.Resource) *walletv1.Resource {
if len(items) == 0 {
return nil
}
return items[randomRobotInt(int64(len(items)))]
}
func activityRequestMeta(requestID string, appCode string) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: requestID,
Caller: "admin-server",
SentAtMs: time.Now().UnixMilli(),
AppCode: appCode,
}
}