// Package modules - 洞府系统模块 // 对齐GDD-31 洞府与个人空间系统设计 package modules import ( "context" "database/sql" "encoding/json" "math/rand" "time" "github.com/heroiclabs/nakama-common/runtime" "github.com/jackc/pgx/v5" ) // RegisterCave 注册洞府相关 RPC。 func RegisterCave(initializer runtime.Initializer) error { rpcs := map[string]func(runtime.Initializer) error{ "CaveService/GetCaveInfo": getCaveInfo, "CaveService/UpgradeCave": upgradeCave, "CaveService/UpgradeFacility": upgradeFacility, "CaveService/CollectResources": collectResources, "CaveService/StartClosedDoor": startClosedDoor, } for path, fn := range rpcs { if err := initializer.RegisterRpc(path, fn); err != nil { return err } } return nil } type getCaveInfoReq struct { CharacterID string `json:"character_id"` } type upgradeCaveReq struct { CharacterID string `json:"character_id"` } type upgradeFacilityReq struct { CharacterID string `json:"character_id"` FacilityType string `json:"facility_type"` // cultivation_room/alchemy_room/forge_room/herb_field/ore_vein } type collectResourcesReq struct { CharacterID string `json:"character_id"` ResourceType string `json:"resource_type"` // herb/ore/spirit_stone } type startClosedDoorReq struct { CharacterID string `json:"character_id"` SessionType string `json:"session_type"` // short/medium/long DurationHours int32 `json:"duration_hours"` } type caveInfoData struct { CharacterID string `json:"character_id"` CaveLevel int32 `json:"cave_level"` MaxLevel int32 `json:"max_level"` Facilities interface{} `json:"facilities"` Resources interface{} `json:"resources"` Defense interface{} `json:"defense"` UpgradeCost interface{} `json:"upgrade_cost"` } type facilityData struct { Type string `json:"type"` Level int32 `json:"level"` Name string `json:"name"` Effect string `json:"effect"` } type resourceData struct { Type string `json:"type"` Level int32 `json:"level"` Quality string `json:"quality"` DailyYield int32 `json:"daily_yield"` LastCollected string `json:"last_collected"` } // 洞府等级配置 var caveLevelConfig = map[int32]struct { MaxFacilityLevel int32 SpiritVeinLevel int32 OreVeinLevel int32 HerbFieldLevel int32 DefenseLevel int32 UpgradeCost float64 }{ 1: {1, 1, 0, 0, 0, 100}, 2: {2, 1, 1, 1, 1, 500}, 3: {3, 2, 2, 2, 2, 2000}, 4: {4, 3, 3, 3, 3, 8000}, 5: {5, 4, 4, 4, 4, 30000}, 6: {6, 5, 5, 5, 5, 100000}, 7: {7, 6, 6, 6, 6, 500000}, 8: {8, 7, 7, 7, 7, 2000000}, 9: {9, 8, 8, 8, 8, 10000000}, } // 设施等级配置 var facilityLevelConfig = map[int32]map[string]struct { Effect string UpgradeCost float64 }{ 1: { "cultivation_room": {"修炼速度+10%", 100}, "alchemy_room": {"炼丹成功率+3%", 100}, "forge_room": {"炼器成功率+2%", 100}, "herb_field": {"草药产出×10", 50}, "ore_vein": {"矿石产出×10", 50}, }, 5: { "cultivation_room": {"修炼速度+55%", 5000}, "alchemy_room": {"炼丹成功率+20%", 5000}, "forge_room": {"炼器成功率+14%", 5000}, "herb_field": {"草药产出×25+中品×10", 3000}, "ore_vein": {"矿石产出×25+中品×10", 3000}, }, 9: { "cultivation_room": {"修炼速度+140%", 500000}, "alchemy_room": {"炼丹成功率+40%", 500000}, "forge_room": {"炼器成功率+30%", 500000}, "herb_field": {"草药产出×15+仙级×5", 300000}, "ore_vein": {"矿石产出×15+仙级×5", 300000}, }, } func getCaveInfo(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 getCaveInfoReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 查询洞府信息(从角色的guild_territories中获取) var caveLevel int32 err := hhdbPool.QueryRow(ctx, ` SELECT COALESCE(gt.level, 1) FROM characters c LEFT JOIN guild_territories gt ON gt.id = ( SELECT id FROM guild_territories WHERE guild_id IN (SELECT id FROM guilds WHERE leader_id = c.id) AND territory_type = 'cave' LIMIT 1 ) WHERE c.id = $1 `, req.CharacterID).Scan(&caveLevel) if err != nil { caveLevel = 1 } config, ok := caveLevelConfig[caveLevel] if !ok { config = caveLevelConfig[1] } // 获取设施信息 facilities := []facilityData{ {Type: "cultivation_room", Level: minInt32(config.SpiritVeinLevel, 9), Name: "修炼室", Effect: "修炼速度+" + int32ToString(config.SpiritVeinLevel*15) + "%"}, {Type: "alchemy_room", Level: minInt32(config.HerbFieldLevel, 9), Name: "炼丹房", Effect: "炼丹成功率+" + int32ToString(config.HerbFieldLevel*5) + "%"}, {Type: "forge_room", Level: minInt32(config.OreVeinLevel, 9), Name: "炼器室", Effect: "炼器成功率+" + int32ToString(config.OreVeinLevel*4) + "%"}, {Type: "herb_field", Level: minInt32(config.HerbFieldLevel, 9), Name: "灵田", Effect: "草药产出×" + int32ToString(config.HerbFieldLevel*3)}, {Type: "ore_vein", Level: minInt32(config.OreVeinLevel, 9), Name: "矿脉", Effect: "矿石产出×" + int32ToString(config.OreVeinLevel*3)}, } return okResp(caveInfoData{ CharacterID: req.CharacterID, CaveLevel: caveLevel, MaxLevel: 9, Facilities: facilities, UpgradeCost: config.UpgradeCost, }, traceID) } func upgradeCave(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 upgradeCaveReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 获取当前洞府等级 var caveLevel int32 err := hhdbPool.QueryRow(ctx, ` SELECT COALESCE(gt.level, 1) FROM characters c LEFT JOIN guild_territories gt ON gt.id = ( SELECT id FROM guild_territories WHERE guild_id IN (SELECT id FROM guilds WHERE leader_id = c.id) AND territory_type = 'cave' LIMIT 1 ) WHERE c.id = $1 `, req.CharacterID).Scan(&caveLevel) if err != nil { caveLevel = 1 } if caveLevel >= 9 { return errResp(8301, "cave already at max level", traceID) } // 检查升级成本 config, ok := caveLevelConfig[caveLevel] if !ok { config = caveLevelConfig[1] } // 升级洞府(简化版,实际应扣除材料) _, err = hhdbPool.Exec(ctx, ` UPDATE guild_territories SET level = level + 1, updated_at = NOW() WHERE guild_id IN (SELECT id FROM guilds WHERE leader_id = $1) AND territory_type = 'cave' `, req.CharacterID) if err != nil { logger.Error("upgrade cave failed: %v", err) // 如果没有洞府记录,创建一个 _, err = hhdbPool.Exec(ctx, ` INSERT INTO guild_territories (guild_id, territory_type, level, params) SELECT id, 'cave', 2, '{}' FROM guilds WHERE leader_id = $1 ON CONFLICT DO NOTHING `, req.CharacterID) if err != nil { return errResp(9002, "internal error", traceID) } } return okResp(map[string]interface{}{ "success": true, "new_level": caveLevel + 1, "cost": config.UpgradeCost, "message": "洞府升级成功", }, traceID) } func upgradeFacility(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 upgradeFacilityReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 简化版:升级设施 logger.Info("Upgrade facility: character=%s type=%s", req.CharacterID, req.FacilityType) return okResp(map[string]interface{}{ "success": true, "facility_type": req.FacilityType, "message": "设施升级成功", }, traceID) } func collectResources(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 collectResourcesReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 获取角色ID var charID string err := hhdbPool.QueryRow(ctx, ` SELECT id FROM characters WHERE player_id = $1 AND status = 'active' ORDER BY created_at DESC LIMIT 1 `, uid).Scan(&charID) if err != nil { return errResp(4002, "character not found", traceID) } // 获取洞府等级 var caveLevel int32 err = hhdbPool.QueryRow(ctx, ` SELECT COALESCE(gt.level, 1) FROM characters c LEFT JOIN guild_territories gt ON gt.id = ( SELECT id FROM guild_territories WHERE guild_id IN (SELECT id FROM guilds WHERE leader_id = c.id) AND territory_type = 'cave' LIMIT 1 ) WHERE c.id = $1 `, charID).Scan(&caveLevel) if err != nil { caveLevel = 1 } // 计算产出 yield := int32(caveLevel * 10) if req.ResourceType == "herb" { yield = int32(caveLevel * 5) } else if req.ResourceType == "ore" { yield = int32(caveLevel * 8) } // 添加随机波动 yield = int32(float64(yield) * (0.8 + rand.Float64()*0.4)) return okResp(map[string]interface{}{ "success": true, "resource_type": req.ResourceType, "quantity": yield, "cave_level": caveLevel, "message": "资源收集成功", }, traceID) } func startClosedDoor(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 startClosedDoorReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 获取角色ID var charID string err := hhdbPool.QueryRow(ctx, ` SELECT id FROM characters WHERE player_id = $1 AND status = 'active' ORDER BY created_at DESC LIMIT 1 `, uid).Scan(&charID) if err != nil { return errResp(4002, "character not found", traceID) } // 计算闭关参数 var durationHours int32 var proficiencyBonus float64 var epiphanyBonus float64 var costSpiritStones float64 switch req.SessionType { case "short": durationHours = 4 proficiencyBonus = 0.30 epiphanyBonus = 0.05 costSpiritStones = 10 case "medium": durationHours = 24 proficiencyBonus = 0.60 epiphanyBonus = 0.15 costSpiritStones = 100 case "long": durationHours = 72 proficiencyBonus = 1.00 epiphanyBonus = 0.30 costSpiritStones = 500 default: return errResp(2001, "invalid session type", traceID) } // 创建闭关记录 _, err = hhdbPool.Exec(ctx, ` INSERT INTO closed_door_sessions (character_id, session_type, duration_hours, cost_spirit_stones, proficiency_bonus, epiphany_bonus, bottleneck_bonus, status, started_at, ends_at) VALUES ($1, $2, $3, $4, $5, $6, 0.10, 'active', NOW(), NOW() + $7 * INTERVAL '1 hour') `, charID, req.SessionType, durationHours, costSpiritStones, proficiencyBonus, epiphanyBonus, durationHours) if err != nil { logger.Error("start closed door failed: %v", err) return errResp(9002, "internal error", traceID) } return okResp(map[string]interface{}{ "success": true, "session_type": req.SessionType, "duration_hours": durationHours, "proficiency_bonus": proficiencyBonus, "epiphany_bonus": epiphanyBonus, "cost_spirit_stones": costSpiritStones, "message": "闭关修炼开始", }, traceID) } // 辅助函数 func minInt32(a, b int32) int32 { if a < b { return a } return b } func int32ToString(n int32) string { return json.Number(string(rune('0' + n%10))).String() }