lawless/server/modules/cave.go

414 行
12 KiB
Go

// 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()
}