2026-07-02 16:55:34 +08:00
|
|
|
|
// Package modules - 世界Boss系统模块
|
|
|
|
|
|
// 对齐GDD-33 世界Boss与大型PVE协作机制
|
|
|
|
|
|
package modules
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"database/sql"
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/heroiclabs/nakama-common/runtime"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// RegisterWorldBoss 注册世界Boss相关 RPC。
|
|
|
|
|
|
func RegisterWorldBoss(initializer runtime.Initializer) error {
|
2026-07-03 21:34:51 +08:00
|
|
|
|
rpcs := map[string]func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, string) (string, error){
|
2026-07-02 16:55:34 +08:00
|
|
|
|
"WorldBossService/GetActiveBosses": getActiveBosses,
|
|
|
|
|
|
"WorldBossService/JoinBossFight": joinBossFight,
|
|
|
|
|
|
"WorldBossService/GetBossRanking": getBossRanking,
|
|
|
|
|
|
}
|
|
|
|
|
|
for path, fn := range rpcs {
|
|
|
|
|
|
if err := initializer.RegisterRpc(path, fn); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type getActiveBossesReq struct {
|
|
|
|
|
|
WorldTier int32 `json:"world_tier"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type joinBossFightReq struct {
|
|
|
|
|
|
BossID string `json:"boss_id"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type getBossRankingReq struct {
|
|
|
|
|
|
BossID string `json:"boss_id"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type worldBossData struct {
|
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
WorldTier int32 `json:"world_tier"`
|
|
|
|
|
|
RealmTier int32 `json:"realm_tier"`
|
|
|
|
|
|
MaxHP int64 `json:"max_hp"`
|
|
|
|
|
|
CurrentHP int64 `json:"current_hp"`
|
|
|
|
|
|
Attack int32 `json:"attack"`
|
|
|
|
|
|
Defense int32 `json:"defense"`
|
|
|
|
|
|
Elements interface{} `json:"elements"`
|
|
|
|
|
|
SpawnTime time.Time `json:"spawn_time"`
|
|
|
|
|
|
DespawnTime time.Time `json:"despawn_time"`
|
|
|
|
|
|
Participants int32 `json:"participants"`
|
|
|
|
|
|
Status string `json:"status"` // spawning/active/defeated/despawned
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type bossRankingData struct {
|
|
|
|
|
|
BossID string `json:"boss_id"`
|
|
|
|
|
|
CharacterID string `json:"character_id"`
|
|
|
|
|
|
CharName string `json:"char_name"`
|
|
|
|
|
|
DamageDealt int64 `json:"damage_dealt"`
|
|
|
|
|
|
Rank int32 `json:"rank"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 世界Boss模板
|
|
|
|
|
|
var worldBossTemplates = map[string]worldBossData{
|
|
|
|
|
|
"ancient_dragon_boss": {
|
|
|
|
|
|
ID: "ancient_dragon_boss", Name: "远古巨龙", WorldTier: 4, RealmTier: 6,
|
|
|
|
|
|
MaxHP: 5000000, Attack: 800, Defense: 500,
|
|
|
|
|
|
Elements: []string{"fire", "lightning"},
|
|
|
|
|
|
Status: "spawning",
|
|
|
|
|
|
},
|
|
|
|
|
|
"chaos_eye": {
|
|
|
|
|
|
ID: "chaos_eye", Name: "混沌之眼", WorldTier: 5, RealmTier: 8,
|
|
|
|
|
|
MaxHP: 20000000, Attack: 3000, Defense: 2000,
|
|
|
|
|
|
Elements: []string{"chaos"},
|
|
|
|
|
|
Status: "spawning",
|
|
|
|
|
|
},
|
|
|
|
|
|
"old_god_avatar": {
|
|
|
|
|
|
ID: "old_god_avatar", Name: "旧神化身", WorldTier: 6, RealmTier: 9,
|
|
|
|
|
|
MaxHP: 100000000, Attack: 10000, Defense: 8000,
|
|
|
|
|
|
Elements: []string{"chaos", "void"},
|
|
|
|
|
|
Status: "spawning",
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getActiveBosses(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
|
|
|
|
traceID := newTraceID()
|
|
|
|
|
|
|
|
|
|
|
|
var req getActiveBossesReq
|
|
|
|
|
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
|
|
|
|
|
return errResp(2001, "invalid payload", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var bosses []worldBossData
|
|
|
|
|
|
for _, boss := range worldBossTemplates {
|
|
|
|
|
|
if boss.WorldTier == req.WorldTier || req.WorldTier == 0 {
|
|
|
|
|
|
bosses = append(bosses, boss)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return okResp(map[string]interface{}{
|
|
|
|
|
|
"bosses": bosses,
|
|
|
|
|
|
"count": len(bosses),
|
|
|
|
|
|
}, traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func joinBossFight(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
|
|
|
|
traceID := newTraceID()
|
|
|
|
|
|
uid := userIDFromCtx(ctx)
|
|
|
|
|
|
if uid == "" {
|
|
|
|
|
|
return errResp(1001, "missing token", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var req joinBossFightReq
|
|
|
|
|
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
|
|
|
|
|
return errResp(2001, "invalid payload", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取角色信息
|
|
|
|
|
|
var charID string
|
|
|
|
|
|
var charName string
|
|
|
|
|
|
var realmTier int32
|
|
|
|
|
|
err := hhdbPool.QueryRow(ctx, `
|
|
|
|
|
|
SELECT id, name, realm_tier FROM characters WHERE player_id = $1 AND status = 'active'
|
|
|
|
|
|
ORDER BY created_at DESC LIMIT 1
|
|
|
|
|
|
`, uid).Scan(&charID, &charName, &realmTier)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errResp(4002, "character not found", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Boss是否存在
|
|
|
|
|
|
boss, ok := worldBossTemplates[req.BossID]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return errResp(8601, "boss not found", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查境界要求
|
|
|
|
|
|
if realmTier < boss.RealmTier-2 {
|
|
|
|
|
|
return errResp(8602, "realm too low for this boss", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加入Boss战(简化版)
|
|
|
|
|
|
logger.Info("Join boss fight: boss=%s player=%s", req.BossID, charName)
|
|
|
|
|
|
|
|
|
|
|
|
return okResp(map[string]interface{}{
|
|
|
|
|
|
"success": true,
|
|
|
|
|
|
"boss_id": req.BossID,
|
|
|
|
|
|
"boss_name": boss.Name,
|
|
|
|
|
|
"character_id": charID,
|
|
|
|
|
|
"character_name": charName,
|
|
|
|
|
|
"message": "已加入Boss战",
|
|
|
|
|
|
}, traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getBossRanking(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
|
|
|
|
traceID := newTraceID()
|
|
|
|
|
|
|
|
|
|
|
|
var req getBossRankingReq
|
|
|
|
|
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
|
|
|
|
|
return errResp(2001, "invalid payload", traceID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟排行榜
|
|
|
|
|
|
ranking := []bossRankingData{
|
|
|
|
|
|
{BossID: req.BossID, CharacterID: "char_1", CharName: "仙尊", DamageDealt: 1000000, Rank: 1},
|
|
|
|
|
|
{BossID: req.BossID, CharacterID: "char_2", CharName: "剑圣", DamageDealt: 800000, Rank: 2},
|
|
|
|
|
|
{BossID: req.BossID, CharacterID: "char_3", CharName: "魔尊", DamageDealt: 600000, Rank: 3},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return okResp(map[string]interface{}{
|
|
|
|
|
|
"boss_id": req.BossID,
|
|
|
|
|
|
"ranking": ranking,
|
|
|
|
|
|
"count": len(ranking),
|
|
|
|
|
|
}, traceID)
|
|
|
|
|
|
}
|