一些检测仍在等待运行
Docs Build / build-and-deploy (push) Waiting to run
- 移除 ConfigManager 配置管理器类 - 移除 GameManager 全局单例管理器类 - 移除 NetworkManager 网络连接管理器类 - 移除 CharacterData 和 ItemData 数据模型类 - 移除 BagScene、BattleScene、LobbyScene 等场景脚本 - 移除 EncounterBubble 和 EventFeedPanel UI组件脚本 - 更新代理邀请文档中的服务器连接方式 - 更新同步状态表格中的代理任务分配信息 - 添加 MiMo 任务完成总结和审查修复记录
413 行
12 KiB
Go
413 行
12 KiB
Go
// Package modules - 洞府系统模块
|
||
// 对齐GDD-31 洞府与个人空间系统设计
|
||
package modules
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"math/rand"
|
||
|
||
"github.com/heroiclabs/nakama-common/runtime"
|
||
hhdb "github.com/honghuang-game/server/internal/db"
|
||
)
|
||
|
||
// RegisterCave 注册洞府相关 RPC。
|
||
func RegisterCave(initializer runtime.Initializer) error {
|
||
rpcs := map[string]func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, 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 := hhdb.Pool.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 := hhdb.Pool.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 = hhdb.Pool.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 = hhdb.Pool.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 := hhdb.Pool.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 = hhdb.Pool.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 := hhdb.Pool.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 = hhdb.Pool.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()
|
||
}
|