修复相关火箭问题
This commit is contained in:
parent
b2362eefe8
commit
6b16f8c96e
@ -274,7 +274,7 @@
|
||||
| `rewards[].grant_id` | 发奖回执 |
|
||||
| `rewards[].status` | 发奖状态 |
|
||||
|
||||
奖励 IM 用于通知本次哪些人中奖。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`。
|
||||
奖励 IM 用于通知本次哪些人中奖。top1 和点火人命中后会发完整配置池,同一用户可能同时收到多条 `top1`、`igniter` 和 1 条在房随机奖励。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`。
|
||||
|
||||
## 客户端处理
|
||||
|
||||
|
||||
@ -74,12 +74,15 @@
|
||||
| 贡献第一奖励 | `pending_launches[].top1_user_id` |
|
||||
| 在房奖励 | 发射命令执行时 Room Cell `OnlineUsers` |
|
||||
|
||||
`reward_stack_policy`:
|
||||
发放规则:
|
||||
|
||||
| 值 | 说明 |
|
||||
| 奖励 | 规则 |
|
||||
| --- | --- |
|
||||
| `allow_stack` | 同一用户可同时拿在房、top1、点火人奖励 |
|
||||
| `priority_only` | 先点火人,再 top1,再在房;同一用户只拿一份 |
|
||||
| 在房奖励 | 每个在线用户从 `inRoomRewards` 按权重随机抽 1 个 |
|
||||
| 贡献第一奖励 | top1 用户获得 `top1Rewards` 配置池内全部奖励 |
|
||||
| 点火人奖励 | 点火用户获得 `igniterRewards` 配置池内全部奖励 |
|
||||
|
||||
同一用户同时是 top1 和点火人时,top1 池和点火人池继续叠加;如果该用户发射瞬间仍在房间,也会再拿 1 个在房随机奖励。
|
||||
|
||||
## IM
|
||||
|
||||
@ -119,7 +122,7 @@
|
||||
| `launch_delay_ms` | 点火到发射延迟 |
|
||||
| `broadcast_enabled` | 是否发飘屏 |
|
||||
| `broadcast_scope` | `region`、`global`、`none` |
|
||||
| `reward_stack_policy` | 奖励叠加策略 |
|
||||
| `reward_stack_policy` | 历史兼容字段;当前特殊身份奖励按上方规则叠加 |
|
||||
| `levels_json` | 5 级阈值、物料和奖励池 |
|
||||
| `gift_fuel_rules_json` | 礼物燃料规则 |
|
||||
|
||||
|
||||
@ -913,21 +913,16 @@ func sortedRocketOnlineUserIDs(users map[int64]*state.RoomUserState) []int64 {
|
||||
return userIDs
|
||||
}
|
||||
|
||||
func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, current *state.RoomState, launch state.RocketPendingLaunch, cfg RoomRocketConfig, levelCfg RoomRocketLevelConfig, inRoomUserIDs []int64) ([]state.RocketRewardGrant, error) {
|
||||
func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, current *state.RoomState, launch state.RocketPendingLaunch, _ RoomRocketConfig, levelCfg RoomRocketLevelConfig, inRoomUserIDs []int64) ([]state.RocketRewardGrant, error) {
|
||||
if current == nil {
|
||||
return nil, nil
|
||||
}
|
||||
rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+2)
|
||||
claimed := make(map[int64]bool, len(inRoomUserIDs)+2)
|
||||
addReward := func(role string, userID int64, pool []RoomRocketRewardItem) error {
|
||||
rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+len(levelCfg.Top1Rewards)+len(levelCfg.IgniterRewards))
|
||||
addRewardItem := func(role string, userID int64, item RoomRocketRewardItem) error {
|
||||
if userID <= 0 {
|
||||
return nil
|
||||
}
|
||||
if cfg.RewardStackPolicy == roomRocketRewardStackPriority && claimed[userID] {
|
||||
return nil
|
||||
}
|
||||
item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool)
|
||||
if !ok {
|
||||
if item.ResourceGroupID <= 0 || item.Weight <= 0 {
|
||||
return nil
|
||||
}
|
||||
grantID, err := s.grantRoomRocketReward(ctx, now, current, launch.RocketID, role, userID, item)
|
||||
@ -944,35 +939,35 @@ func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, cu
|
||||
GrantID: grantID,
|
||||
Status: roomRocketGrantStatus,
|
||||
})
|
||||
claimed[userID] = true
|
||||
return nil
|
||||
}
|
||||
addRandomReward := func(role string, userID int64, pool []RoomRocketRewardItem) error {
|
||||
item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return addRewardItem(role, userID, item)
|
||||
}
|
||||
addAllRewards := func(role string, userID int64, pool []RoomRocketRewardItem) error {
|
||||
for _, item := range pool {
|
||||
if err := addRewardItem(role, userID, item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.RewardStackPolicy == roomRocketRewardStackPriority {
|
||||
// 去重策略下先发特殊身份奖励,再发普通在房奖励,避免 top1/点火人被普通池占位。
|
||||
if err := addReward(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, userID := range inRoomUserIDs {
|
||||
if err := addReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil {
|
||||
// 在房奖励是大面积发放池,每个在线用户只按权重抽一个奖项,避免房间人数放大单次火箭成本。
|
||||
if err := addRandomReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rewards, nil
|
||||
}
|
||||
|
||||
for _, userID := range inRoomUserIDs {
|
||||
if err := addReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil {
|
||||
// top1 和点火人是确定身份权益:命中后发完整配置池;同一用户同时命中两个身份时继续叠加。
|
||||
if err := addAllRewards(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := addReward(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil {
|
||||
if err := addAllRewards(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rewards, nil
|
||||
|
||||
@ -433,6 +433,77 @@ func TestRoomRocketLaunchGrantsLockedTop1AndIgniterAfterLeave(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomRocketLaunchStacksAllSpecialPoolsAndRandomInRoomReward(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repository := mysqltest.NewRepository(t)
|
||||
now := &fixedRoomRocketClock{now: time.Date(2026, 5, 20, 13, 0, 0, 0, time.UTC)}
|
||||
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{
|
||||
{BillingReceiptId: "receipt-stack", GiftPointAdded: 100, HeatValue: 100, GiftTypeCode: "normal"},
|
||||
}}
|
||||
svc := newRocketTestService(t, repository, wallet, now)
|
||||
config := rocketConfigForTest(100, 1_000)
|
||||
config.Levels[0].InRoomRewards = []roomservice.RoomRocketRewardItem{
|
||||
{RewardItemID: "level-1-in-room-a", ResourceGroupID: 1001, Weight: 1, DisplayName: "In Room A"},
|
||||
{RewardItemID: "level-1-in-room-b", ResourceGroupID: 1101, Weight: 1, DisplayName: "In Room B"},
|
||||
}
|
||||
config.Levels[0].Top1Rewards = []roomservice.RoomRocketRewardItem{
|
||||
{RewardItemID: "level-1-top1-a", ResourceGroupID: 2001, Weight: 1, DisplayName: "Top 1 A"},
|
||||
{RewardItemID: "level-1-top1-b", ResourceGroupID: 2101, Weight: 1, DisplayName: "Top 1 B"},
|
||||
}
|
||||
config.Levels[0].IgniterRewards = []roomservice.RoomRocketRewardItem{
|
||||
{RewardItemID: "level-1-igniter-a", ResourceGroupID: 3001, Weight: 1, DisplayName: "Igniter A"},
|
||||
{RewardItemID: "level-1-igniter-b", ResourceGroupID: 3101, Weight: 1, DisplayName: "Igniter B"},
|
||||
}
|
||||
writeRocketConfig(t, ctx, repository, config)
|
||||
|
||||
roomID := "room-rocket-stack"
|
||||
ownerID := int64(401)
|
||||
igniterID := int64(402)
|
||||
createRocketRoom(t, ctx, svc, roomID, ownerID, 9003)
|
||||
joinRocketRoom(t, ctx, svc, roomID, igniterID)
|
||||
|
||||
if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
|
||||
Meta: rocketMeta(roomID, igniterID, "stack"),
|
||||
TargetType: "user",
|
||||
TargetUserId: ownerID,
|
||||
GiftId: "gift-stack",
|
||||
GiftCount: 1,
|
||||
}); err != nil {
|
||||
t.Fatalf("ignite stack rocket failed: %v", err)
|
||||
}
|
||||
|
||||
now.now = now.now.Add(1100 * time.Millisecond)
|
||||
if err := svc.SweepRoomRocketLaunchings(ctx); err != nil {
|
||||
t.Fatalf("sweep stacked room rocket launches failed: %v", err)
|
||||
}
|
||||
|
||||
info, err := svc.GetRoomRocket(ctx, &roomv1.GetRoomRocketRequest{
|
||||
Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID},
|
||||
RoomId: roomID,
|
||||
ViewerUserId: ownerID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("get stacked rocket after launch failed: %v", err)
|
||||
}
|
||||
rewards := info.GetRocket().GetState().GetLastRewards()
|
||||
if len(rewards) != 6 {
|
||||
t.Fatalf("launch must keep two in-room random grants plus all top1/igniter grants: %+v", rewards)
|
||||
}
|
||||
if countRocketRewards(rewards, ownerID, "in_room") != 1 {
|
||||
t.Fatalf("owner must receive exactly one random in-room reward: %+v", rewards)
|
||||
}
|
||||
if countRocketRewards(rewards, igniterID, "in_room") != 1 {
|
||||
t.Fatalf("top1 igniter must still receive exactly one random in-room reward: %+v", rewards)
|
||||
}
|
||||
if countRocketRewards(rewards, igniterID, "top1") != 2 || countRocketRewards(rewards, igniterID, "igniter") != 2 {
|
||||
t.Fatalf("top1 igniter must receive every configured special reward: %+v", rewards)
|
||||
}
|
||||
if !hasRocketGrant(wallet.grants, igniterID, 2001) || !hasRocketGrant(wallet.grants, igniterID, 2101) ||
|
||||
!hasRocketGrant(wallet.grants, igniterID, 3001) || !hasRocketGrant(wallet.grants, igniterID, 3101) {
|
||||
t.Fatalf("wallet must grant every top1 and igniter resource group: %+v", wallet.grants)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomRocketUTCResetCancelsCrossDayIgnited(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repository := mysqltest.NewRepository(t)
|
||||
@ -676,6 +747,16 @@ func hasRocketGrant(grants []*walletv1.GrantResourceGroupRequest, userID int64,
|
||||
return false
|
||||
}
|
||||
|
||||
func countRocketRewards(rewards []*roomv1.RoomRocketRewardGrant, userID int64, role string) int {
|
||||
count := 0
|
||||
for _, reward := range rewards {
|
||||
if reward.GetUserId() == userID && reward.GetRewardRole() == role {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func rocketRewardUserIDs(rewards []*roomv1.RoomRocketRewardGrant) []int64 {
|
||||
userIDs := make([]int64, 0, len(rewards))
|
||||
for _, reward := range rewards {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user